Bladeren bron

学生端错题

wangguoxi 6 maanden geleden
bovenliggende
commit
6dc93dd655

+ 38 - 16
src/App.vue

@@ -1,29 +1,51 @@
 <template>
-  <div id="app" ref="app">
-
-    <router-view></router-view>
-  </div>
+    <div id="app" ref="app">
+        <router-view></router-view>
+    </div>
 </template>
 
 <script>
+import user from "@/http/api/user";
+import { setToken } from "@/utils/auth";
+import { encrypt } from "@/utils/jsencrypt";
 export default {
-  name: "App",
-  mounted() {
-    // autofit.init({
-    //     dh: 900,
-    //     dw: 1440,
-    //     el:"body",
-    //     resize: true
-    // })
-  },
+    name: "App",
+    mounted() {
+        // INFO: 本地调试,自动登录
+        if (process.env.NODE_ENV === 'development') {
+            this.SubmitLogin();
+        }
+    },
+    methods: {
+        SubmitLogin() {
+            const username = '99689820198800';
+            const password = '123456';
+            const type = '2';
+            sessionStorage.setItem('schoolType', type);
+            user.loginEmailPass({
+                username: username,
+                password: encrypt(password),
+            }).then((res) => {
+                if (res.code === 200) {
+                    this.$store.dispatch("user/CLEAR_LOCAL_STORAGE");//清空本地存储
+                    setToken(res.data.tokenValue);
+                    this.$store.dispatch("user/SET_TOKEN", res.data.tokenValue);
+                    this.$store.dispatch("user/SET_SCHOOL_LOGO", res.data.schoolLogoUrl);//设置学校logo
+                    this.$store.dispatch("user/SET_SCHOOL_WEB_SITE_ID", res.data.cloudMonitorSiteId || '');//设置学校网站id
+                    this.$store.dispatch("user/getUserInfoByToken");//获取用户登录信息
+                    this.$router.push("/studentAnalysisReport/list");
+                }
+            });
+        },
+    }
 };
 </script>
 
 <style lang="scss">
 @use "@/styles/setting.scss";
+
 #app {
-  height: 100%;
-  width: 100%;
+    height: 100%;
+    width: 100%;
 }
-
 </style>

+ 0 - 7
src/components/PaperImage.vue

@@ -155,7 +155,6 @@ export default {
     {
       handler(newVal, oldVal)
       {
-         console.log("地址变化了",this.paperImgUrl);
         this.InitData();//初始化数据
       },
       deep: true ,// 如果需要深度监听数组内部对象的变化
@@ -188,19 +187,14 @@ export default {
     this.CacheTypicalError.src=require('../assets/tool/model_2.png');
     this.CacheExcellentAnswer.src=require('../assets/tool/model_1.png');
     this.CacheAllWrong.onload=()=>{
-      console.log("缓存全错图片加载完成");
     };
     this.CacheAllRight.onload=()=>{
-      console.log("缓存全对图片加载完成");
     };
     this.CacheHalfRight.onload=()=>{
-      console.log("缓存半对图片加载完成");
     };
     this.CacheTypicalError.onload=()=>{
-      console.log("缓存典型错误图片加载完成");
     };
     this.CacheExcellentAnswer.onload=()=>{
-      console.log("缓存优秀答案图片加载完成");
     };
     window.addEventListener('resize', this.handleResize);
      // 添加全局鼠标释放事件监听器,处理异常情况
@@ -234,7 +228,6 @@ export default {
       if (this.$refs.paperContainer) {
         this.$refs.paperContainer.addEventListener('contextmenu', this.handleRightClick);
         // this.$refs.paperContainer.addEventListener('click', this.hideContextMenu);
-        console.log('事件监听器已添加');
       } else {
         console.error('paperContainer 未找到');
       }

+ 11 - 0
src/http/api/errorQuestion.js

@@ -0,0 +1,11 @@
+import { get, post } from "../common/http";
+import base from "../common/base";
+
+function url(url) {
+    return base.prefix + url;
+}
+
+export const queryStudentErrorQuestion = (data) => get(url("/api/v1/studentError/queryStudentErrorQuestion"), data);
+export const markStudentErrorQuestion = (data) => get(url("/api/v1/studentError/markStudentErrorQuestion"), data);
+export const downloadStudentErrorQuestion = (data) => get(url("/api/v1/studentError/downloadStudentErrorQuestion"), data);
+

+ 2 - 1
src/http/api/reportStudent.js

@@ -83,9 +83,10 @@ const jointSchoolReport = {
 //单校接口
 const schoolReport = {
     //学生端查询考试列表(单校)
+    // TODO: 王国西 临时修改
     queryStudentExamDataList(data) {
         return get(
-            base.prefix + "/api/v1/oneSchoolReport/examOneSchoolDataList",
+            base.prefix + "/api/v1/studentData/queryStudentExamDataList",
             data
         );
     },

+ 2 - 1
src/http/api/user.js

@@ -8,7 +8,8 @@ const user = {
   //当学登录学生的用户信息
   getUserInfoByToken()
   {
-    return get(base.prefix + '/api/v1/student/userInfo')
+    // TODO: 王国西 暂时修改接口地址
+    return get(base.prefix + '/api/v1/student_user/find_user_info')
   },
   //修改密码
   changeMinePassword(data) {

+ 106 - 100
src/router/index.js

@@ -5,125 +5,131 @@ Vue.use(Router);
 
 const originalPush = Router.prototype.push;
 Router.prototype.push = function push(location) {
-  return originalPush.call(this, location).catch((err) => err);
+    return originalPush.call(this, location).catch((err) => err);
 };
 // 其他路由
 let constantRoutes = [
-  {
-    path: "/index",
-    component: () => import("@/views/login/login"),
-    meta: {
-      title: "otherLogin",
+    {
+        path: "/index",
+        component: () => import("@/views/login/login"),
+        meta: {
+            title: "otherLogin",
+        },
+    },
+    {
+        path: "/",
+        redirect: "/index"//redirect: "/studentAnalysisReport"
     },
-  },
-  {
-    path: "/",
-    redirect: "/index"//redirect: "/studentAnalysisReport"
-  },
 ];
 // 学生端分析报告
 let studentAnalysisReport = {
-  path: "/studentAnalysisReport",
-  redirect: "/studentAnalysisReport/list",
-  component: () => import("@/views/analysisReport/index"),
-  children: [
-    {
-      path: "/studentAnalysisReport/list", // 报告列表
-      meta: {
-        title: "分析报告",
-      },
-      component: () => import("@/views/analysisReport/studentPage/list/mainPage"),
-    },
-    {
-      name: "reportDetails",
-      path: "/studentAnalysisReport/reportDetails",
-      component: () => import("@/views/analysisReport/studentPage/mainPage"),
-      children: [
+    path: "/studentAnalysisReport",
+    redirect: "/studentAnalysisReport/list",
+    component: () => import("@/views/analysisReport/index"),
+    children: [
         {
-          path: "scrolReport",
-          component: () =>
-            import("@/views/analysisReport/studentPage/scrolReport/transcript"),
-          meta: {
-            title: "学生分析",
-          },
-        }
-      ],
-    },
-  ],
+            path: "/studentAnalysisReport/list", // 报告列表
+            meta: {
+                title: "分析报告",
+            },
+            component: () => import("@/views/analysisReport/studentPage/list/mainPage"),
+        },
+        {
+            name: "reportDetails",
+            path: "/studentAnalysisReport/reportDetails",
+            component: () => import("@/views/analysisReport/studentPage/mainPage"),
+            children: [
+                {
+                    path: "scrolReport",
+                    component: () => import("@/views/analysisReport/studentPage/scrolReport/transcript"),
+                    meta: {
+                        title: "学生分析",
+                    },
+                },
+                {
+                    path: "personalWrongQuestions",
+                    component: () => import("@/views/analysisReport/wrongQuestion/index"),
+                    meta: {
+                        title: "个性化错题",
+                    },
+                },
+            ],
+        },
+    ],
 };
 const createRouter = () =>
-  new Router({
-    mode: "hash",
-    routes: [
-      ...constantRoutes,
-      studentAnalysisReport
-    ],
-    scrollBehavior(to, from, savedPosition) {
-      if (savedPosition) {
-        return savedPosition;
-      } else {
-        return {
-          x: 0,
-          y: 0,
-        };//路由切换时,滚动条回到顶部
-      }
-    },
-  });
+    new Router({
+        mode: "hash",
+        routes: [
+            ...constantRoutes,
+            studentAnalysisReport
+        ],
+        scrollBehavior(to, from, savedPosition) {
+            if (savedPosition) {
+                return savedPosition;
+            } else {
+                return {
+                    x: 0,
+                    y: 0,
+                };//路由切换时,滚动条回到顶部
+            }
+        },
+    });
 // 添加统计分析脚本的函数
 function addAnalyticsScript() {
-  
-  // 移除已存在的统计脚本
-  let  existingScript = document.getElementById('_bxtj');
-  // console.log('移除已存在的统计脚本...',existingScript);
-  while  (existingScript) {
-    existingScript.remove();
-    existingScript = document.getElementById('_bxtj');
-  }
-   // 移除已存在的统计脚本
-  let  existingScriptFrame= document.getElementById('_bxtjiframe');
-  // console.log('移除已存在的统计脚本...',existingScriptFrame);
-  while (existingScriptFrame) {
-    existingScriptFrame.remove();
-    existingScriptFrame = document.getElementById('_bxtjiframe');
-  }
-  console.log('添加统计分析脚本...');
-  // 等待DOM加载完成
-  setTimeout(() => {
-    var _s = document.createElement('script');
-    _s.setAttribute('type','text/javascript');
-    _s.setAttribute('id','_bxtj');
-    _s.setAttribute('async',true);
-    
-    // 获取当前用户信息
-    const userInfo = store.state.user.userInfo || {};
-    const currentUser = userInfo.loginName || ''; // 当前用户名
-    const subSiteName = userInfo.schoolName || '';
-    const mySiteId = localStorage.getItem("user_schoolWebSiteId") || ''; // 你的站点ID
-    console.log("当前学校站点id",userInfo, mySiteId);
-    if(mySiteId !='') {
-      // 构建URL(按照原始代码格式)
-      let src = `https://bjedures.bjedu.cn/bjjw_logdb/bxlog.js?user=${currentUser}&id=${mySiteId}`;
-      if (subSiteName) {
-        src += `&subSiteName=${encodeURIComponent(subSiteName)}`;
-      }
-      _s.setAttribute('src', src);
-      var body = document.getElementsByTagName('body');
-      body[0].appendChild(_s);
-      console.log('统计分析脚本已添加到body中。');
+
+    // 移除已存在的统计脚本
+    let existingScript = document.getElementById('_bxtj');
+    // console.log('移除已存在的统计脚本...',existingScript);
+    while (existingScript) {
+        existingScript.remove();
+        existingScript = document.getElementById('_bxtj');
     }
-  }, 1000);//1秒后添加
+    // 移除已存在的统计脚本
+    let existingScriptFrame = document.getElementById('_bxtjiframe');
+    // console.log('移除已存在的统计脚本...',existingScriptFrame);
+    while (existingScriptFrame) {
+        existingScriptFrame.remove();
+        existingScriptFrame = document.getElementById('_bxtjiframe');
+    }
+    console.log('添加统计分析脚本...');
+    // 等待DOM加载完成
+    setTimeout(() => {
+        var _s = document.createElement('script');
+        _s.setAttribute('type', 'text/javascript');
+        _s.setAttribute('id', '_bxtj');
+        _s.setAttribute('async', true);
+
+        // 获取当前用户信息
+        const userInfo = store.state.user.userInfo || {};
+        const currentUser = userInfo.loginName || ''; // 当前用户名
+        const subSiteName = userInfo.schoolName || '';
+        const mySiteId = localStorage.getItem("user_schoolWebSiteId") || ''; // 你的站点ID
+        console.log("当前学校站点id", userInfo, mySiteId);
+        if (mySiteId != '') {
+            // 构建URL(按照原始代码格式)
+            let src = `https://bjedures.bjedu.cn/bjjw_logdb/bxlog.js?user=${currentUser}&id=${mySiteId}`;
+            if (subSiteName) {
+                src += `&subSiteName=${encodeURIComponent(subSiteName)}`;
+            }
+            _s.setAttribute('src', src);
+            var body = document.getElementsByTagName('body');
+            body[0].appendChild(_s);
+            console.log('统计分析脚本已添加到body中。');
+        }
+    }, 1000);//1秒后添加
 }
 let router = createRouter();
 router.beforeEach((to, from, next) => {
-  if (to.meta.title) {
-    document.title = "慧教研-" + to.meta.title;
-  }
-  next();
+    if (to.meta.title) {
+        document.title = "慧教研-" + to.meta.title;
+    }
+    next();
 });
 // 路由后置守卫:添加统计脚本
 router.afterEach(() => {
-  // 添加统计分析脚本
-  addAnalyticsScript()
+    // 添加统计分析脚本
+    addAnalyticsScript()
 });
 
 export default router;

+ 1 - 0
src/store/modules/user.js

@@ -192,6 +192,7 @@ const actions = {
 
   //根据token获取用户信息
   getUserInfoByToken({commit}){
+    console.log('==========')
     return new Promise((resolve, reject) => {
       user.getUserInfoByToken().then((res) => {
         if(res.code == 200){

+ 20 - 0
src/styles/common.scss

@@ -10399,4 +10399,24 @@ body {
   justify-content: center;
   display: flex;
   align-items: center;
+}
+
+.mm_btn {
+  display: block;
+  width: 130px;
+  height: 40px;
+  line-height: 40px;
+  text-align: center;
+  font-size: 14px;
+  outline: none;
+  background: transparent;
+  color: #606266;
+  border: none;
+  cursor: pointer;
+  border-radius: 4px;
+
+  &.active {
+      background-color: #2E64FA ;
+      color: #ffffff;
+  }
 }

+ 51 - 74
src/views/analysisReport/studentPage/list/mainPage.vue

@@ -5,24 +5,16 @@
       <FiltersItem :filtersData="filtersData" @selectItem="ChangeFilters" />
       <div class="page_jg"></div>
     </div>
-    <div
-      class="list_content"
-      v-loading="loading"
-      element-loading-text="加载中……"
-      element-loading-spinner="el-icon-loading"
-      element-loading-background="#ffffff"
-    >
+    <div class="list_content" v-loading="loading" element-loading-text="加载中……" element-loading-spinner="el-icon-loading"
+      element-loading-background="#ffffff">
       <div class="list_item" v-for="item in TablePageData" :key="item.examData._id">
         <div class="item_header">
           <div class="header_left">
-            <div
-              class="exam_type"
-              :style="{
-                background: $global.getExamBackgroundColor(
-                  item.examData.examType
-                ),
-              }"
-            >
+            <div class="exam_type" :style="{
+              background: $global.getExamBackgroundColor(
+                item.examData.examType
+              ),
+            }">
               {{ GxamTypeList(item.examData.examType) }}
             </div>
             <div class="exam_title">{{ item.examData.examName }}</div>
@@ -32,51 +24,28 @@
             </div>
           </div>
           <div class="header_right">
-            <span class="exam_time"
-              >考试时间:{{ item.examData.examDate }}</span
-            >
-            <span class="exam_time"
-              >考试年级:{{ item.examData.gradeName }}</span
-            >
-            <span class="exam_time"
-              >报告更新时间:{{ item.examData.analysisTime }}</span
-            >
+            <span class="exam_time">考试时间:{{ item.examData.examDate }}</span>
+            <span class="exam_time">考试年级:{{ item.examData.gradeName }}</span>
+            <span class="exam_time">报告更新时间:{{ item.examData.analysisTime }}</span>
           </div>
         </div>
         <div class="item_content">
-          <div
-            class="course_img"
-            v-for="(course, i) in item?.examData?.examSubjectVoList || []"
-            :key="i"
-            @click="GoDetail(item, course,item?.examData?.examSubjectVoList || [])"
-          >
-            <img
-              :src="
-                require(`@/assets/fxReport/${GetSubjectsName(
-                  course.subjectName,
-                  course.subjectNum
-                )}`)
-              "
-            />
+          <div class="course_img" v-for="(course, i) in item?.examData?.examSubjectVoList || []" :key="i"
+            @click="GoDetail(item, course, item?.examData?.examSubjectVoList || [])">
+            <img :src="require(`@/assets/fxReport/${GetSubjectsName(course.subjectName, course.subjectNum)}`)" />
             <span class="paper_remark" v-if="course.subjectNum">
-              <el-tooltip
-                :disabled="!course.subjectNum || course.subjectName.length <= 4"
-                placement="top"
-                :content="course.subjectName"
-              >
-                <span
-                  :style="{
-                    color: GetSubjectsColor(
-                      course.subjectName,
-                      course.subjectNum
-                    ),
-                    'border-color': GetSubjectsColor(
-                      course.subjectName,
-                      course.subjectNum
-                    ),
-                  }"
-                  >{{ course.subjectName }}</span
-                >
+              <el-tooltip :disabled="!course.subjectNum || course.subjectName.length <= 4" placement="top"
+                :content="course.subjectName">
+                <span :style="{
+                  color: GetSubjectsColor(
+                    course.subjectName,
+                    course.subjectNum
+                  ),
+                  'border-color': GetSubjectsColor(
+                    course.subjectName,
+                    course.subjectNum
+                  ),
+                }">{{ course.subjectName }}</span>
               </el-tooltip>
             </span>
           </div>
@@ -85,14 +54,8 @@
       <div class="no_content_data" v-if="isNoData">暂无数据</div>
     </div>
     <div class="page_pagination" v-if="pageInfo.total > 10">
-      <el-pagination
-        @current-change="ChangePage"
-        background
-        layout="prev, pager, next"
-        :page-size="10"
-        :current-page="pageInfo.pageNum"
-        :total="pageInfo.total"
-      >
+      <el-pagination @current-change="ChangePage" background layout="prev, pager, next" :page-size="10"
+        :current-page="pageInfo.pageNum" :total="pageInfo.total">
       </el-pagination>
     </div>
     <div class="page_jg_20"></div>
@@ -163,12 +126,12 @@ export default {
     this.GetLocalFilterData(); //从本地读取数据
     this.GetListData(true); //获取列表数据
   },
-  computed:{
+  computed: {
     TablePageData() {
       const { pageSize, pageNum } = this.pageInfo;
       const start = (pageNum - 1) * pageSize;
       const end = start + pageSize;
-      return this.listData.slice(start,end);
+      return this.listData.slice(start, end);
     }
   },
   methods: {
@@ -393,6 +356,7 @@ export default {
       // localStorage.setItem('reportExamId',reportExamItem._id)//分析报告考试主Id
       // localStorage.setItem('reportExamCourseId', course.id);//单科的考试科目id
       // localStorage.setItem('reportExamCourseCode',course.subjectCode);//选择的科目code
+      localStorage.setItem("ExamInfoItem", JSON.stringify(item)); //分析管理考试id
       localStorage.setItem("reportExamItem", JSON.stringify(reportExamItem)); //考试信息
       localStorage.setItem("reportExamCourseItem", JSON.stringify(course)); //选择的考生科目信息
 
@@ -437,8 +401,8 @@ export default {
         let subjectItem = {
           label: item.subjectName,
           value: item.subjectCode,
-          examLevel:examItem?.examData?.examLevel ?? '',//1-联考 2-单校
-          contrastExamIds:examItem?.historyExamIds ?? [],//多次考试任务对比ID,不包含当前任务ID	
+          examLevel: examItem?.examData?.examLevel ?? '',//1-联考 2-单校
+          contrastExamIds: examItem?.historyExamIds ?? [],//多次考试任务对比ID,不包含当前任务ID	
           ...item
         };
         subjectList.push(subjectItem);
@@ -452,9 +416,9 @@ export default {
       ); //选择的项
       if (selectedItem == undefined) {
         this.$message({
-            message: "该科目没有数据!",
-            type: 'error',
-            offset:100
+          message: "该科目没有数据!",
+          type: 'error',
+          offset: 100
         });
         return;
       }
@@ -474,9 +438,9 @@ export default {
         (item) => item.value == this.filterData[0].value
       );
       let filterObject = {
-        examLevel:courseObj.examLevel,//1-联考 2-单校
-        contrastExamIds:courseObj.contrastExamIds,//多次考试任务对比ID,不包含当前任务ID	
-        examId:courseObj.examId,//考试id	
+        examLevel: courseObj.examLevel,//1-联考 2-单校
+        contrastExamIds: courseObj.contrastExamIds,//多次考试任务对比ID,不包含当前任务ID	
+        examId: courseObj.examId,//考试id	
         subjectCode: courseObj.subjectCode, //科目code
         subjectGroupType: courseObj.subjectGroupType, //是否为组合科目 1为组合科目 0为非组合科目
         subjectName: courseObj.subjectName,//科目名称
@@ -513,6 +477,7 @@ export default {
   height: auto;
   min-height: calc(100% - 240px);
   min-height: 400px;
+
   // height: calc(100% - 240px);
   // overflow-y: auto;
   // overflow-x: hidden;
@@ -522,6 +487,7 @@ export default {
     border-radius: 10px 10px 10px 10px;
     border: 1px solid #dcdfe6;
     margin-bottom: 20px;
+
     .item_header {
       width: 100%;
       height: 48px;
@@ -530,6 +496,7 @@ export default {
       display: flex;
       justify-content: space-between;
       align-items: center;
+
       .header_left {
         display: flex;
         justify-content: flex-start;
@@ -537,6 +504,7 @@ export default {
         height: 100%;
         padding-left: 10px;
         gap: 10px;
+
         .exam_type {
           width: 40px;
           height: 30px;
@@ -551,6 +519,7 @@ export default {
           font-size: 18px;
           color: #666666;
         }
+
         .title_score {
           width: 32px;
           height: 20px;
@@ -561,6 +530,7 @@ export default {
           line-height: 20px;
           border-radius: 4px;
         }
+
         .title_Level {
           width: 20px;
           height: 20px;
@@ -580,6 +550,7 @@ export default {
         align-items: center;
         gap: 16px;
         padding-right: 10px;
+
         .exam_time {
           font-weight: 400;
           font-size: 14px;
@@ -587,6 +558,7 @@ export default {
         }
       }
     }
+
     .item_content {
       width: 100%;
       min-height: 120px;
@@ -596,6 +568,7 @@ export default {
       gap: 20px;
       box-sizing: border-box;
       flex-wrap: wrap;
+
       .course_img {
         position: relative;
         width: 75px;
@@ -604,6 +577,7 @@ export default {
         box-shadow: 4px 4 8px 0px rgba(0, 0, 0, 0.08);
         border-radius: 4px 4px 4px 4px;
         border: 1px solid #f3f3f3;
+
         .paper_remark {
           display: inline-block;
           position: absolute;
@@ -614,6 +588,7 @@ export default {
           bottom: 0;
           margin: 66px auto 0;
           text-align: center;
+
           span {
             box-sizing: border-box;
             display: inline-block;
@@ -627,9 +602,11 @@ export default {
             white-space: nowrap;
           }
         }
+
         img {
           width: 100%;
         }
+
         &:hover {
           outline: 2px solid #2e64fa;
           cursor: pointer;

+ 200 - 157
src/views/analysisReport/studentPage/mainPage.vue

@@ -1,177 +1,220 @@
 <template>
-  <!-- 分析报告详情总页面 -->
-  <div class="analysis_main">
-    <div class="main_header" ref="mainHeader">
-      <div class="header_left">
-        <span class="back_button" @click="GoBack">
-          <i class="iconfont icon_return"></i>返回
-        </span>
-        <span class="header_title">{{ pageName }}</span>
-      </div>
-      <div class="header_right">
-        <div class="select_list" v-if="isShowFilter">
-          <el-select style="width: 120px" v-model="item.value" :placeholder="'请选择' + item.name" class="select_item"
-            @change="ChangeFilters({ index: index, value: item.value })" v-for="(item, index) in filterData" :key="index"
-            v-show="item.list.length > 1">
-            <el-option v-for="item in item.list" :key="item.value" :label="item.label" :value="item.value"></el-option>
-          </el-select>
+    <!-- 分析报告详情总页面 -->
+    <div class="analysis_main">
+        <div class="main_header" ref="mainHeader">
+            <div class="header_left">
+                <span class="back_button" @click="GoBack">
+                    <i class="iconfont icon_return"></i>返回
+                </span>
+                <span class="header_title">{{ pageName }}</span>
+            </div>
+            <div class="header_right">
+                <div class="select_list" v-if="isShowFilter">
+                    <el-select style="width: 120px" v-model="item.value" :placeholder="'请选择' + item.name"
+                        class="select_item" @change="ChangeFilters({ index: index, value: item.value })"
+                        v-for="(item, index) in filterData" :key="index" v-show="item.list.length > 1">
+                        <el-option v-for="item in item.list" :key="item.value" :label="item.label"
+                            :value="item.value"></el-option>
+                    </el-select>
+                </div>
+            </div>
         </div>
-      </div>
-    </div>
-    <div class="main_content">
-      <div class="content_right" ref="rightContent" @scroll="ScrollChange">
-        <div class="content_right_scroll" ref="contentRightScroll">
-          <div class="page_filter" ref="filterContent">
-            <FiltersItem :filtersData="filterData" @selectItem="ChangeFilters"></FiltersItem>
-          </div>
-          <router-view ref="child"></router-view>
+
+        <div class="main_content">
+            <div class="content_right" ref="rightContent" @scroll="ScrollChange">
+                <div class="content_right_scroll" ref="contentRightScroll">
+                    <div class="mm_body">
+                        <div class="left">
+                            <button class="mm_btn mb_10" :class="{active: activeBtn === pathOne}" @click="toPage(pathOne)">成绩分析</button>
+                            <button class="mm_btn" :class="{active: activeBtn === pathTwo}" @click="toPage(pathTwo)">个性化错题</button>
+                        </div>
+                        <div class="right">
+                            <div class="page_filter" ref="filterContent">
+                                <FiltersItem :filtersData="filterData" @selectItem="ChangeFilters"></FiltersItem>
+                            </div>
+                            <router-view ref="child"></router-view>
+                        </div>
+                    </div>
+                </div>
+            </div>
         </div>
-      </div>
     </div>
-  </div>
 </template>
 
 <script>
 import FiltersItem from "@/components/FiltersItem_ruoyan.vue";
 export default {
-  components: { FiltersItem },
-  computed: {
-    pageName() {
-      // let examName = this.$store.state.report.examSelectItem.examName;
-      // if (examName) {
-      //   return examName.split("_")[0];
-      // }
-      return this.$store.state.report.examSelectItem.examName
-    },
-    updateScrollTop() {
-      return this.$store.state.report.updateScrollTop;//监听改变滚动条参数 
-    },
-  },
-  data() {
-    return {
-      isShowFilter: false, //是否显示筛选条件
-      filterData: [
-        {
-          name: "科目名称",
-          value: '0',
-          type: "subjectName",
-          list: [], //选项
-        }
-      ],
-      resizeTimer:null
-    };
-  },
-  created() {
-    if (this.$store.state.report.filterData.length > 0) {
-      //有数据
-      this.filterData = this.$store.state.report.filterData;
-    }
-    // 绑定 resize 事件
-    window.addEventListener('resize', this.HandleWidthChange);
-    this.$nextTick(()=>{
-      this.SetHeadWidth();
-    })
-  },
-  methods: {
-    // 处理宽度变化的逻辑
-    HandleWidthChange() {
-      // 防抖:避免窗口 resize 时频繁触发(100ms 内只执行一次)
-      clearTimeout(this.resizeTimer);
-      this.resizeTimer = setTimeout(() => {
-        this.SetHeadWidth();
-      }, 100);
-    },
-    SetHeadWidth(){
-      if(this.$refs.contentRightScroll){
-        const scrollDivWidth = this.$refs.contentRightScroll.offsetWidth;
-        const headDiv = this.$refs.mainHeader;
-        headDiv.style.width = `${scrollDivWidth}px`;
-      }
+    components: { FiltersItem },
+    computed: {
+        pageName() {
+            // let examName = this.$store.state.report.examSelectItem.examName;
+            // if (examName) {
+            //   return examName.split("_")[0];
+            // }
+            return this.$store.state.report.examSelectItem.examName
+        },
+        updateScrollTop() {
+            return this.$store.state.report.updateScrollTop;//监听改变滚动条参数 
+        },
     },
-    // 筛选事件
-    ChangeFilters(e) {
-      this.filterData[e.index].value = e.value;
-      // 选中科目数据
-      let courseObj = this.filterData[0].list.find(item => item.value == this.filterData[0].value);
-      let filterObject = {
-        examLevel: courseObj.examLevel,//1-联考 2-单校
-        contrastExamIds: courseObj.contrastExamIds,//多次考试任务对比ID,不包含当前任务ID	
-        examId: courseObj.examId,//考试id	
-        subjectCode: courseObj.subjectCode, //科目code
-        subjectGroupType: courseObj.subjectGroupType, //是否为组合科目 1为组合科目 0为非组合科目
-        subjectName: courseObj.subjectName,//科目名称
-        isTotal: courseObj.isTotal //是否为总分科目 1为总分 0为非总分
-      };
-      //设置是否是总分
-      this.$store.commit("report/set_state", {
-        key: "isTotalScore",
-        value: courseObj.isTotal == 1 || courseObj.subjectGroupType == 1,//1为总分 0为非总分  1为组合科目 0为非组合科目
-      });
-
-      localStorage.setItem('reportExamCourseId', courseObj.subjectId);//单科的考试科目id
-      localStorage.setItem('reportIsTotalScore', courseObj.isTotal == 1 || courseObj.subjectGroupType == 1)
-      console.log("打印筛选数据", filterObject);
-      this.$store.dispatch("report/UpdateFilterObject", filterObject);
+    data() {
+        return {
+            isShowFilter: false, //是否显示筛选条件
+            filterData: [
+                {
+                    name: "科目名称",
+                    value: '0',
+                    type: "subjectName",
+                    list: [], //选项
+                }
+            ],
+            resizeTimer: null,
+            activeBtn: this.$route.path,
+            pathOne: '/studentAnalysisReport/reportDetails/scrolReport',
+            pathTwo: '/studentAnalysisReport/reportDetails/personalWrongQuestions',
+        };
     },
 
-    // 滚动条事件
-    ScrollChange() {
-      let scrollTop = this.$refs.rightContent.scrollTop;
-      const filterContent = this.$refs.filterContent;
-      let scrollHeight = 242;
-      if (filterContent) {
-        scrollHeight = filterContent.clientHeight;
-      }
-      if (scrollTop >= scrollHeight) {
-        this.isShowFilter = true;
-
-      }
-      else {
-        this.isShowFilter = false;
-      }
-      this.$store.commit("report/set_state", {
-        key: "isShowFilter",
-        value: this.isShowFilter,
-      });
-      if (this.$refs.child && typeof this.$refs.child.GetScrollTop === 'function') {
-        this.$refs.child.GetScrollTop(scrollTop);
-      }
-    },
-    //重置滚动条
-    ResetScroll() {
-      this.$nextTick(() => {
-        if (this.$refs.rightContent) {
-          this.$refs.rightContent.scrollTop = 0;
+    created() {
+        if (this.$store.state.report.filterData.length > 0) {
+            //有数据
+            this.filterData = this.$store.state.report.filterData;
         }
-      })
+        // 绑定 resize 事件
+        window.addEventListener('resize', this.HandleWidthChange);
+        this.$nextTick(() => {
+            this.SetHeadWidth();
+        })
     },
 
-    //返回按钮点击
-    GoBack() {
-      this.$router.push("/studentAnalysisReport/list");
-    },
-  },
-  watch: {
-    '$route'(to, from) {
-      console.log('路由变化', to, from);
-      this.ResetScroll();//重置滚动条
-    },//路由变化 重置页面滚动条位置
-
-    updateScrollTop: {
-      handler(newVal) {
-        console.log("updateScrollTop changed:", newVal);
-        this.ResetScroll();//重置滚动条
-      },
+    methods: {
+        toPage(type) {
+            this.activeBtn = type;
+            this.$router.push(type);
+        },
+
+        // 处理宽度变化的逻辑
+        HandleWidthChange() {
+            // 防抖:避免窗口 resize 时频繁触发(100ms 内只执行一次)
+            clearTimeout(this.resizeTimer);
+            this.resizeTimer = setTimeout(() => {
+                this.SetHeadWidth();
+            }, 100);
+        },
+
+        SetHeadWidth() {
+            if (this.$refs.contentRightScroll) {
+                const scrollDivWidth = this.$refs.contentRightScroll.offsetWidth;
+                const headDiv = this.$refs.mainHeader;
+                headDiv.style.width = `${scrollDivWidth}px`;
+            }
+        },
+
+        // 筛选事件
+        ChangeFilters(e) {
+            this.filterData[e.index].value = e.value;
+            // 选中科目数据
+            let courseObj = this.filterData[0].list.find(item => item.value == this.filterData[0].value);
+            let filterObject = {
+                examLevel: courseObj.examLevel,//1-联考 2-单校
+                contrastExamIds: courseObj.contrastExamIds,//多次考试任务对比ID,不包含当前任务ID	
+                examId: courseObj.examId,//考试id	
+                subjectCode: courseObj.subjectCode, //科目code
+                subjectGroupType: courseObj.subjectGroupType, //是否为组合科目 1为组合科目 0为非组合科目
+                subjectName: courseObj.subjectName,//科目名称
+                isTotal: courseObj.isTotal //是否为总分科目 1为总分 0为非总分
+            };
+            //设置是否是总分
+            this.$store.commit("report/set_state", {
+                key: "isTotalScore",
+                value: courseObj.isTotal == 1 || courseObj.subjectGroupType == 1,//1为总分 0为非总分  1为组合科目 0为非组合科目
+            });
+
+            localStorage.setItem('reportExamCourseId', courseObj.subjectId);//单科的考试科目id
+            localStorage.setItem('reportIsTotalScore', courseObj.isTotal == 1 || courseObj.subjectGroupType == 1)
+            console.log("打印筛选数据", filterObject);
+            this.$store.dispatch("report/UpdateFilterObject", filterObject);
+        },
+
+        // 滚动条事件
+        ScrollChange() {
+            let scrollTop = this.$refs.rightContent.scrollTop;
+            const filterContent = this.$refs.filterContent;
+            let scrollHeight = 242;
+            if (filterContent) {
+                scrollHeight = filterContent.clientHeight;
+            }
+            if (scrollTop >= scrollHeight) {
+                this.isShowFilter = true;
+
+            }
+            else {
+                this.isShowFilter = false;
+            }
+            this.$store.commit("report/set_state", {
+                key: "isShowFilter",
+                value: this.isShowFilter,
+            });
+            if (this.$refs.child && typeof this.$refs.child.GetScrollTop === 'function') {
+                this.$refs.child.GetScrollTop(scrollTop);
+            }
+        },
+        //重置滚动条
+        ResetScroll() {
+            this.$nextTick(() => {
+                if (this.$refs.rightContent) {
+                    this.$refs.rightContent.scrollTop = 0;
+                }
+            })
+        },
+
+        //返回按钮点击
+        GoBack() {
+            this.$router.push("/studentAnalysisReport/list");
+        },
     },
+    watch: {
+        '$route'(to, from) {
+            this.ResetScroll();//重置滚动条
+        },//路由变化 重置页面滚动条位置
 
-  },
-  beforeUnmount() {
-    // 解绑事件,避免内存泄漏
-    window.removeEventListener('resize', this.HandleWidthChange);
-    // 清除计时器
-    clearTimeout(this.resizeTimer);
-  }
+        updateScrollTop: {
+            handler(newVal) {
+                this.ResetScroll();//重置滚动条
+            },
+        },
+
+    },
+    beforeUnmount() {
+        // 解绑事件,避免内存泄漏
+        window.removeEventListener('resize', this.HandleWidthChange);
+        // 清除计时器
+        clearTimeout(this.resizeTimer);
+    }
 };
 </script>
 
-<style scoped lang="scss"></style>
+<style scoped lang="scss">
+.mm_body {
+    display: flex;
+    width: 100%;
+
+    .left {
+        width: 170px;
+        padding: 20px;
+        border-radius: 4px;
+        background-color: #ffffff;
+        height: fit-content;
+        box-sizing: border-box;
+
+        .mb_10 {
+            margin-bottom: 10px;
+        }
+    }
+
+    .right {
+        flex: 1;
+        margin-left: 10px;
+    }
+}
+</style>

+ 129 - 0
src/views/analysisReport/wrongQuestion/Download.vue

@@ -0,0 +1,129 @@
+<template>
+    <el-dialog title="个性化提升手册" :visible="visible" width="530px" @abort="handleClose">
+        <div class="box">
+            <div class="item" :class="{ active: active === 1 }" @click="changeAnswer(1)">答案解析在每题后</div>
+            <div class="item" :class="{ active: active === 2 }" @click="changeAnswer(2)">答案解析在尾部</div>
+            <div class="item" :class="{ active: active === 0 }" @click="changeAnswer(0)">无答案解析</div>
+        </div>
+        <span slot="footer" class="dialog-footer">
+            <el-button @click="handleClose">取 消</el-button>
+            <el-button type="primary" @click="download">下载</el-button>
+        </span>
+    </el-dialog>
+</template>
+
+<script>
+import { downloadStudentErrorQuestion } from '../../../http/api/errorQuestion';
+import { ExtractFilename, DownloadFile, CreateLoadingInstance } from './tools'
+
+export default {
+    props: {
+        visible: {
+            type: Boolean,
+            default: false
+        },
+        examId: {
+            type: [String, Number],
+            required: true
+        },
+        subjectCode: {
+            type: [String, Number],
+            required: true
+        }
+    },
+
+    data() {
+        return {
+            active: 1
+        };
+    },
+
+    methods: {
+        handleClose() {
+            this.$emit('update:visible', false);
+        },
+
+        changeAnswer(value) {
+            this.active = value;
+        },
+
+        download() {
+            const loadingInstance = CreateLoadingInstance()
+            let isAnswer = this.active;
+
+            if (this.active === 1 || this.active === 2) {
+                isAnswer = 1;
+            }
+
+            downloadStudentErrorQuestion({
+                examId: this.examId,
+                subjectCode: this.subjectCode,
+                isAnswer
+            }).then(response => {
+                const blob2 = new Blob([response.data], {
+                    type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
+                });
+
+                const contentDisposition = response.headers['content-disposition'];
+                const fileName = ExtractFilename(contentDisposition)
+                DownloadFile(blob2, fileName)
+            }).catch(error => {
+                this.$message.error('下载失败,请稍后重试');
+            }).finally(() => {
+                loadingInstance.close();
+            });
+        }
+    }
+};
+</script>
+
+<style scoped lang="scss">
+.box {
+    display: flex;
+    justify-content: space-between;
+    padding: 20px 0;
+
+    .item {
+        text-align: center;
+        font-size: 14px;
+        line-height: 22px;
+        color: #909399;
+        padding: 15px 20px;
+        background: #EDEFF6;
+        border-radius: 4px;
+        cursor: pointer;
+
+        &.active {
+            color: #2B65FF;
+            background: #fff;
+            border: 2px solid #2B65FF;
+            box-shadow: 0 2px 6px rgba(43, 101, 255, 0.12);
+            position: relative;
+
+            /* 右下角的蓝色三角与对勾 */
+            &::after {
+                content: '';
+                position: absolute;
+                right: 0;
+                bottom: 0;
+                border: 12px solid #2B65FF;
+                border-top-color: transparent;
+                border-left-color: transparent;
+            }
+
+            /* 对勾图形(使用伪元素并旋转) */
+            &::before {
+                content: '√';
+                color: #fff;
+                position: absolute;
+                right: 5px;
+                bottom: -5px;
+                border-top-color: transparent;
+                border-left-color: transparent;
+                // transform: rotate(45deg);
+                z-index: 1;
+            }
+        }
+    }
+}
+</style>

+ 468 - 0
src/views/analysisReport/wrongQuestion/index.vue

@@ -0,0 +1,468 @@
+<template>
+    <div>
+        <div class="select_box">
+            <el-radio-group v-model="errorType" size="medium" @change="_queryStudentErrorQuestion">
+                <el-radio-button :label="-1">全部试题({{ allErrorCount }})</el-radio-button>
+                <el-radio-button :label="0">未掌握({{ unMasterCount }})</el-radio-button>
+                <el-radio-button :label="1">已掌握({{ masterCount }})</el-radio-button>
+                <el-radio-button :label="2">复习题({{ reviewCount }})</el-radio-button>
+            </el-radio-group>
+        </div>
+
+        <div class="content">
+            <div class="right_btn">
+                <el-button size="medium" @click="downloadDialogVisible = true">下载错题本</el-button>
+                <el-button type="primary" size="medium">下载个性化提升手册</el-button>
+            </div>
+
+            <Download :visible.sync="downloadDialogVisible" :examId="examId" :subjectCode="subjectCode" />
+
+            <div v-for="(question, index) in questionList" :key="question.questionId">
+                <div class="question_card">
+                    <div class="card_top">
+                        <div class="card_top_info">
+                            <div class="left">
+                                <span class="number">{{ index + 1 }}</span>
+                                <span class="gray">试题类型:</span>
+                                <span class="black">{{ question.questionType }}</span>
+                                <span class="tag">高频错题</span>
+                            </div>
+                        </div>
+
+                        <div class="question_content" v-if="question.sourceType === 1"
+                            v-html="question.questionData.questionStem"></div>
+                        <div class="question_content" v-if="question.sourceType === 2">
+                            <img :src="question.questionImg" alt="" width="100%">
+                        </div>
+
+                        <div class="card_footer" style="margin-bottom: 20px;">
+                            <div class="footer_item">
+                                <span class="gray">满分:</span>
+                                <span class="blue">{{ question.fullScore }}</span>
+                                <span class="gray">分</span>
+                            </div>
+                            <div class="footer_item">
+                                <span class="gray">我的得分:</span>
+                                <span class="blue">{{ question.score }}</span>
+                                <span class="gray">分</span>
+                            </div>
+                            <div class="footer_item">
+                                <span class="gray">难度</span>
+                                <span class="blue">一般</span>
+                            </div>
+                        </div>
+
+                        <div class="card_footer">
+                            <div class="gray">知识点:</div>
+                            <template v-if="question.knowledgePoint && question.knowledgePoint.length > 0">
+                                <el-tag type="success" size="small" v-for="(know, index) in question.knowledgePoint"
+                                    :key="index">
+                                    {{ know }}
+                                </el-tag>
+                            </template>
+                        </div>
+                    </div>
+
+                    <div class="card_buttom">
+                        <div class="btn_box">
+                            <span class="black" size="small" @click="question.answerShow = !question.answerShow">
+                                我的答案
+                            </span>
+                            <span class="black" size="small" @click="question.parseShow = !question.parseShow">
+                                查看解析
+                            </span>
+
+                            <template v-if="question.errorStatus === 0">
+                                <template v-if="errorType === -1 || errorType === 0">
+                                    <span class="black" size="small" @click="_markStudentErrorQuestion(question, 1)">
+                                        标记为已掌握
+                                    </span>
+                                    <span class="black" size="small" @click="_markStudentErrorQuestion(question, 2)">
+                                        加入复习本
+                                    </span>
+                                </template>
+                            </template>
+
+                            <template v-if="errorType === 1 || question.errorStatus === 1">
+                                <span class="black" size="small" @click="_markStudentErrorQuestion(question, 0)">
+                                    标记为未掌握
+                                </span>
+                            </template>
+
+                            <template v-if="errorType === 2 || question.errorStatus === 2">
+                                <span class="black" size="small" @click="_markStudentErrorQuestion(question, 0)">
+                                    移除复习本
+                                </span>
+                            </template>
+                        </div>
+
+                        <div class="content" v-if="question.parseShow">
+                            <div class="content_inner" v-if="question.sourceType === 1"
+                                v-html="question.questionData.analysis"></div>
+                            <div class="content_inner" v-if="question.sourceType === 2">
+                                <img :src="question.parseImg" alt="" width="100%" />
+                            </div>
+                        </div>
+
+                        <div class="content" v-if="question.answerShow">
+                            <div class="content_inner" v-if="question.sourceType === 1"
+                                v-html="question.questionData.answer"></div>
+                            <div class="content_inner" v-if="question.sourceType === 2">
+                                <img :src="question.answerImg" alt="" width="100%" />
+                            </div>
+                        </div>
+                    </div>
+                </div>
+
+                <div class="question_card" v-if="question.variationQuestion">
+                    <!-- TODO: 这个地方后端暂无数据 -->
+                    <div class="card_top">
+                        <div class="card_top_info">
+                            <div class="left">
+                                <span class="number">1</span>
+                                <span class="gray">试题类型:</span>
+                                <span class="black">选择题</span>
+                                <span class="tag">高频错题</span>
+                            </div>
+                            <div class="right">
+                                <span class="gray">来源:</span>
+                                <span class="black">这是一场考试名称</span>
+                            </div>
+                        </div>
+
+                        <div class="question_content">这是题目内容</div>
+
+                        <div class="yuwen">
+
+                            <span class="btn_span active">1</span>
+                            <span class="btn_span">2</span>
+
+                            <div class="question_list"></div>
+                        </div>
+
+                        <div class="card_footer">
+                            <div class="footer_item">
+                                <span class="gray">满分:</span>
+                                <span class="blue">5.0</span>
+                                <span class="gray">分</span>
+                            </div>
+                            <div class="footer_item">
+                                <span class="gray">我的得分:</span>
+                                <span class="blue">5.0</span>
+                                <span class="gray">分</span>
+                            </div>
+                            <div class="footer_item">
+                                <span class="gray">难度</span>
+                                <span class="blue">一般</span>
+                            </div>
+                        </div>
+
+                        <div class="card_footer">
+                            <div class="gray">知识点:</div>
+                            <el-tag type="success" size="small">标签二</el-tag>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <div style="text-align: right;">
+                <el-pagination background layout="prev, pager, next" :total="pageParam.total"
+                    :page-size="pageParam.pageSize" :current-page="pageParam.pageNum"
+                    @current-change="handleCurrentChange">
+                </el-pagination>
+            </div>
+
+        </div>
+    </div>
+</template>
+
+<script>
+import {
+    queryStudentErrorQuestion,
+    markStudentErrorQuestion,
+    downloadStudentErrorQuestion,
+} from '../../../http/api/errorQuestion';
+import Download from './Download.vue';
+import loadImg from './loadImg.js'
+import { Loading } from 'element-ui';
+export default {
+    components: {
+        Download,
+    },
+
+    data() {
+        return {
+            pageParam: {
+                pageNum: 1,
+                pageSize: 10,
+                total: 0,
+            },
+            questionList: [],
+            allErrorCount: 0,
+            unMasterCount: 0,
+            masterCount: 0,
+            reviewCount: 0,
+            answerUrls: [],
+            uploadPaperUrls: [],
+            errorType: -1, // -1全部 0未掌握 1已掌握 2复习题
+
+            examId: null,
+            examType: null,
+            gradeCode: null,
+
+            downloadDialogVisible: false,
+        };
+    },
+
+    computed: {
+        subjectCode() {
+            return this.$store.state.report.filterObject.subjectCode;
+        }, //分析报告公共参数变量
+    },
+
+    watch: {
+        subjectCode(newVal, oldVal) {
+            if (newVal !== oldVal) {
+                this._queryStudentErrorQuestion();
+            }
+        },
+    },
+
+    created() {
+        const ExamInfoItem = JSON.parse(localStorage.getItem("ExamInfoItem") || "{}");
+        const reportExamCourseItem = JSON.parse(localStorage.getItem("reportExamCourseItem") || "{}");
+        this.examId = reportExamCourseItem.examId;
+        this.gradeCode = ExamInfoItem.examData.gradeCode;
+        this.examType = ExamInfoItem.examData.examType;
+    },
+
+    async mounted() {
+        await this._queryStudentErrorQuestion();
+    },
+
+    methods: {
+        async _queryStudentErrorQuestion() {
+            const instance = Loading.service({ fullscreen: true });
+            const { subjectCode, gradeCode, examType, examId, errorType, pageParam: { pageNum, pageSize } } = this
+            const res = await queryStudentErrorQuestion({ subjectCode, gradeCode, examType, examId, errorType, pageNum, pageSize });
+
+            if (res.code === 200) {
+                const { allErrorCount, unMasterCount, masterCount, reviewCount, questionList, answerUrls, uploadPaperUrls } = res.data.records[0];
+
+                this.allErrorCount = allErrorCount;
+                this.masterCount = masterCount;
+                this.unMasterCount = unMasterCount;
+                this.reviewCount = reviewCount;
+                this.answerUrls = answerUrls;
+                this.uploadPaperUrls = uploadPaperUrls;
+                this.pageParam.total = res.data.total * 1;
+
+                for (let i = 0; i < questionList.length; i++) {
+                    const { sourceType, titleCoordinates, answerCoordinates, parseCoordinates } = questionList[i]
+                    questionList[i].answerShow = false;
+                    questionList[i].parseShow = false;
+                    if (sourceType === 2) {
+                        const questionImg = await loadImg(this.uploadPaperUrls, titleCoordinates);
+                        questionList[i].questionImg = questionImg;
+
+                        const answerImg = await loadImg(this.answerUrls, answerCoordinates);
+                        questionList[i].answerImg = answerImg;
+
+                        const parseImg = await loadImg(this.answerUrls, parseCoordinates);
+                        questionList[i].parseImg = parseImg;
+                    }
+                }
+
+                this.questionList = questionList;
+            }
+
+            instance.close();
+        },
+
+        async _markStudentErrorQuestion(question, markStatus) {
+            const res = await markStudentErrorQuestion({
+                questionId: question.questionId,
+                examId: this.examId,
+                subjectCode: this.subjectCode,
+                markStatus
+            })
+
+            if (res.code === 200) {
+                this.$message.success('操作成功');
+                await this._queryStudentErrorQuestion();
+            } else {
+                this.$message.error(res.message || '操作失败');
+            }
+        },
+
+        async handleCurrentChange(page) {
+            this.pageParam.pageNum = page;
+            await this._queryStudentErrorQuestion();
+        },
+    },
+}
+</script>
+
+<style scoped lang="scss">
+.select_box {
+    background-color: #ffffff;
+    border-radius: 4px;
+    padding: 10px;
+    margin-bottom: 10px;
+}
+
+.content {
+    background-color: #ffffff;
+    border-radius: 4px;
+    padding: 20px;
+    margin-bottom: 10px;
+}
+
+.right_btn {
+    display: flex;
+    justify-content: flex-end;
+    margin-bottom: 10px;
+}
+
+.question_card {
+    margin-bottom: 10px;
+    border: 1px solid #DCDFE6;
+    border-radius: 10px;
+
+    .card_top {
+        padding: 20px;
+    }
+
+    .card_top_info {
+        display: flex;
+        justify-content: space-between;
+        margin-bottom: 20px;
+
+        .left {
+            display: flex;
+            align-items: center;
+
+            span {
+                margin-right: 10px;
+                font-size: 14px;
+            }
+
+            .tag {
+                color: #ffffff;
+                padding: 5px;
+                background-color: #F56C6C;
+                border-radius: 2px;
+                position: relative;
+
+                &::before {
+                    content: '';
+                    position: absolute;
+                    left: -5px;
+                    top: 50%;
+                    transform: translateY(-50%);
+                    width: 0;
+                    height: 0;
+                    border-top: 5px solid transparent;
+                    border-bottom: 5px solid transparent;
+                    border-right: 5px solid #EE6666;
+                }
+            }
+        }
+    }
+
+    .number {
+        color: #ffffff;
+        font-size: 14px;
+        padding: 5px 9px;
+        background-color: #2E64FA;
+        border-radius: 4px;
+    }
+
+    .btn_span {
+        color: #2E64FA;
+        font-size: 14px;
+        padding: 5px 9px;
+        border: 1px solid #2E64FA;
+        border-radius: 4px;
+        cursor: pointer;
+        margin-right: 10px;
+
+        &.active {
+            color: #ffffff;
+            background-color: #2E64FA;
+            border-radius: 4px;
+        }
+    }
+
+    .gray {
+        color: #999;
+    }
+
+    .black {
+        color: #333;
+    }
+
+    .blue {
+        color: #2E64FA;
+    }
+
+    .question_content {
+        margin-bottom: 20px;
+    }
+
+    .yuwen {
+        margin-top: 20px;
+
+        .question_list {
+            margin-top: 20px;
+            height: 100px;
+            padding-top: 20px;
+            border-top: 1px solid #EBEEF5;
+        }
+    }
+
+    .card_footer {
+        display: flex;
+        align-items: center;
+        padding-top: 20px;
+        border-top: 1px solid #EBEEF5;
+
+        .footer_item {
+            margin-right: 40px;
+
+            span {
+                margin-right: 5px;
+            }
+        }
+    }
+
+    .card_buttom {
+        background-color: #DCDFE6;
+        padding: 15px 20px;
+
+        .btn_box {
+            display: flex;
+            justify-content: flex-end;
+            margin-bottom: 20px;
+
+            span {
+                margin-left: 20px;
+                cursor: pointer;
+            }
+        }
+
+        .content {
+            padding: 20px;
+            background-color: #ffffff;
+
+            .content_inner {
+                border: 2px dotted #DCDFE6;
+                border-radius: 4px;
+                padding: 10px;
+            }
+        }
+    }
+
+
+}
+</style>

+ 106 - 0
src/views/analysisReport/wrongQuestion/loadImg.js

@@ -0,0 +1,106 @@
+export default async function loadImg(urlList, CoordinateList) {
+    /**
+     * urlList: 图片地址数组
+     * 数据格式为 [url1, url2, url3]
+     * 
+     * CoordinateList: 坐标数组
+     * 数据格式为 [
+     *      { page: 1, x: 10, y: 20, w: 100, h: 150 },
+     *      { page: 2, x: 30, y: 40, w: 120, h: 160 },
+     * ]
+     * 也可能为
+     * [
+     *      { page: 2, x: 30, y: 40, w: 120, h: 160 },
+     *      { page: 4, x: 50, y: 60, w: 140, h: 180 },
+     * ]
+     * 
+     * 注意:page 为 1 时,对应 urlList 中的第一个图片地址
+     * 
+     * 功能描述:将多张图片根据坐标裁剪并合并为一张图片。
+     * 注意:
+     * (1)有可能裁剪区域的宽度不一致,合并后图片宽度取最大宽度,其他图片按比例缩放。
+     * (2)裁剪区域按顺序垂直排列,之间无间距。
+     * (3)page 可能不从 1 开始,且不连续。我们只需根据 page 值找到对应图片即可。
+     * 返回值:返回一个 Promise,resolve 合并后的图片的 Base64 数据 URL。
+     * 
+    */
+
+    if (!Array.isArray(urlList) || !Array.isArray(CoordinateList)) {
+        return null;
+    }
+
+    if (CoordinateList.length === 0 || urlList.length === 0) {
+        return null;
+    }
+
+    let maxWidth = 0;
+    for (let coord of CoordinateList) {
+        if (coord.w > maxWidth) {
+            maxWidth = coord.w;
+        }
+    }
+
+    const imgList = []
+
+    for (let urlIndex = 0; urlIndex < urlList.length; urlIndex++) {
+        const url = urlList[urlIndex];
+        for (let i = 0; i < CoordinateList.length; i++) {
+            const coord = CoordinateList[i];
+            const page = coord.page;
+            // 根据 maxWidth 计算缩放比例
+            const scale = maxWidth / coord.w;
+            
+            // 计算高度
+            coord.w = maxWidth;
+            coord.h = coord.h * scale;
+
+            if (page - 1 === urlIndex) {
+                const img = await loadImage(url);
+                const tempCanvas = document.createElement('canvas');
+                const tempCtx = tempCanvas.getContext('2d');
+                tempCanvas.width = coord.w;
+                tempCanvas.height = coord.h;
+
+                // 此处按 计算后的宽高进行裁剪
+                tempCtx.drawImage(img, coord.x, coord.y, coord.w, coord.h, 0, 0, coord.w, coord.h);
+                imgList.push({
+                    img: tempCanvas,
+                    width: coord.w,
+                    height: coord.h
+                });
+            }
+        }
+    }
+
+    // 创建最终合并的画布
+    const finalCanvas = document.createElement('canvas');
+    const finalCtx = finalCanvas.getContext('2d');
+    finalCanvas.width = maxWidth;
+    let totalHeight = 0;
+    for (let i = 0; i < imgList.length; i++) {
+        totalHeight += imgList[i].height;
+    }
+    finalCanvas.height = totalHeight;
+
+    let currentY = 0;
+    for (let i = 0; i < imgList.length; i++) {
+        const img = imgList[i].img;
+        finalCtx.drawImage(img, 
+            0, 0, maxWidth, imgList[i].height, 
+            0, currentY, maxWidth, imgList[i].height
+        );
+        currentY += imgList[i].height;
+    }
+
+    return finalCanvas.toDataURL('image/png');
+}
+
+function loadImage(url) {
+    return new Promise((resolve, reject) => {
+        const img = new Image();
+        img.crossOrigin = "Anonymous";
+        img.src = url;
+        img.onload = () => resolve(img);
+        img.onerror = (err) => reject(err);
+    })
+}

+ 61 - 0
src/views/analysisReport/wrongQuestion/tools.js

@@ -0,0 +1,61 @@
+import { Loading } from 'element-ui';
+
+/**
+ * 从 HTTP 头的 Content-Disposition 字段中提取文件名
+ * @param {string} contentDisposition - HTTP 头的 Content-Disposition 字段值
+ * @returns {string|null} 提取的文件名,若未找到则返回 null
+*/
+export function ExtractFilename(contentDisposition) {
+    if (!contentDisposition) return null;
+
+    // 匹配 filename= 后面的内容(直到分号或字符串结束)
+    const match = contentDisposition.match(/filename=([^;]+)/i);
+    if (!match) return null;
+
+    let filename = match[1].trim();
+
+    // 去除可能的引号
+    if (filename.startsWith('"') && filename.endsWith('"')) {
+        filename = filename.slice(1, -1);
+    }
+
+    try {
+        // URL 解码
+        return decodeURIComponent(filename);
+    } catch (e) {
+        console.warn('URL解码失败,返回原始文件名:', e);
+        return filename;
+    }
+}
+
+
+/**
+ * 触发浏览器下载文件
+ * @param {Blob} blob - 要下载的文件的 Blob 对象
+ * @param {string} fileName - 下载时使用的文件名
+*/
+export function DownloadFile(blob, fileName) {
+    const url = window.URL.createObjectURL(blob);
+    const link = document.createElement('a');
+    link.href = url;
+    link.download = fileName;
+    link.click();
+    window.URL.revokeObjectURL(url);
+    link.remove();
+}
+
+/**
+ * 创建一个加载中实例
+ * @param {Object} options - 加载中配置选项
+ * @returns {Object} 加载中实例
+*/
+export function CreateLoadingInstance(options) {
+    options = Object.assign({
+        lock: true,
+        text: '正在处理...',
+        spinner: 'el-icon-loading',
+        background: 'rgba(0, 0, 0, 0.7)'
+    }, options || {});
+
+    return Loading.service(options);
+}

+ 3 - 3
src/views/login/login.vue

@@ -28,9 +28,9 @@ export default {
       const code = this.$route.query.code || '';
       const pw = this.$route.query.pw || '';
       const type = this.$route.query.type || '1';//1:单校 2:联校
-      if(!code && !pw){
-        window.location.href = base.STUDENT_LOGIN;//跳转到教师端登录页面
-      }
+      // if(!code && !pw){
+      //   window.location.href = base.STUDENT_LOGIN;//跳转到教师端登录页面
+      // }
       sessionStorage.setItem('schoolType', type);//1:单校 2:联校
       const params = {
         "password": decodeURI(pw),//解密 encrypt(decodeURI(pw))

+ 0 - 16
src/views/userInfo/components/userSetting/studentName.vue

@@ -442,7 +442,6 @@ import { encrypt } from "@/utils/jsencrypt";
             };
 
             this.$api.user.getUserStudentList(param).then(res=>{
-                console.log("打印用户管理学生名单列表",res);
                 if(res.code==200)
                 {
                     this.studentList = res.data.records || [];
@@ -455,7 +454,6 @@ import { encrypt } from "@/utils/jsencrypt";
         //切换入学年份
         ChangeYear()
         {
-            console.log("打印学生全部学年",this.enrollmentYear)
             this.enrollmentYearList.forEach(item=>{
                 if(item.value==this.enrollmentYear)
                 {
@@ -465,15 +463,12 @@ import { encrypt } from "@/utils/jsencrypt";
                 }
             })
             this.ChangeLevelCode();
-            console.log("打印切换后的学段",this.levelCodeList);
             this.ChangeFilter();
         },
 
         //切换学段
         ChangeLevelCode()
         {
-            console.log("打印学生全部学段",this.levelCodeList)
-            console.log("打印levelCode",this.levelCode)
             this.levelCodeList.forEach(item=>{
                 if(item.value==this.levelCode)
                 {
@@ -504,7 +499,6 @@ import { encrypt } from "@/utils/jsencrypt";
         GetSelectLevelTypeList()
         {
             this.$api.user.getSelectLevelTypeList().then(res=>{
-                console.log("打印用户管理筛选数据列表",res);
                 if(res.code==200)
                 {
                     this.enrollmentYearList = res.data || [];//入学年份
@@ -513,7 +507,6 @@ import { encrypt } from "@/utils/jsencrypt";
                     // this.levelCodeList=res.data[0].child || [];//学段列表
                     let list=[];
                     res.data.forEach(item=>{
-                        console.log("打印学段列表",item);
                         item.child.forEach(childItem=>{
                             
                             list.push(childItem);
@@ -570,7 +563,6 @@ import { encrypt } from "@/utils/jsencrypt";
         //编辑学生
         EditorStudent(item)
         {
-            console.log("打印编辑学生信息",item)
             this.editStudentData.dialogShow = true;   
             this.editStudentData.studentRegistrationId=item.registrationCode;//账号     
             this.editStudentData.username=item.studentName;//姓名
@@ -589,8 +581,6 @@ import { encrypt } from "@/utils/jsencrypt";
                     this.editorRegistrationList = levelItem.child || [];
                 }
             }
-            // console.log(this.editorRegistrationList);
-
         },
         //重置密码
         ResetPassWord(item)
@@ -613,7 +603,6 @@ import { encrypt } from "@/utils/jsencrypt";
         //禁用或者启用学生账号
         ChangeStudentStatus(item)
         {
-            console.log("打印当前的学生",item)
             let text=this.statusPage=='enableUse'?'禁用':'启用';
             this.$confirm(`确认要${text}用户   ${item.studentName}(${item.registrationCode}) 吗?`, '提示', {
                 confirmButtonText: '确定',
@@ -643,9 +632,7 @@ import { encrypt } from "@/utils/jsencrypt";
         //切换学生学籍类型
         ChangeStudentRegistrationCode()
         {
-            console.log("打印切换学生学籍类型事件",this.editStudentData);
             this.editStudentData.studentRegistrationTypeName=this.editorRegistrationList.find(option=>option.value===this.editStudentData.studentRegistrationCode).label;
-            console.log("打印切换学生学籍类型事件",this.editStudentData);
         },
         //确定编辑学生信息
         ConfirmUpdateStudent()
@@ -662,9 +649,7 @@ import { encrypt } from "@/utils/jsencrypt";
                 "studentId": this.editStudentData.studentId,//学生id
                 "studentName": this.editStudentData.username//学生姓名
             };
-            console.log("编辑学生参数",param);
             this.$api.user.updateUserStudent(param).then(res=> {
-                console.log("打印保存编辑学生信息",res);
                 if(res.code==200)
                 {
                     this.editStudentData.dialogShow=false;
@@ -787,7 +772,6 @@ import { encrypt } from "@/utils/jsencrypt";
                 useDefault: this.useDefault
             }
             this.$api.user.resetPassWord(param).then(res=>{
-                console.log("重置密码结果",res);
                 if(res.code==200)
                 {
                     this.$message({

+ 0 - 10
src/views/userInfo/components/userSetting/teachName.vue

@@ -362,7 +362,6 @@ import { encrypt } from "@/utils/jsencrypt";
 
                 
                 this.$api.user.getTeacherList(param).then(res=>{
-                    console.log("打印教师名单列表返回结果",res);
                     if(res.code == 200)
                     {
                         this.teacherList = res.data.records || [];
@@ -463,7 +462,6 @@ import { encrypt } from "@/utils/jsencrypt";
             //重置密码
             ResetPassWord(item)
             {
-                console.log("打印item",item);
                 this.editPassWordData={
                     dialogShow:true,
                     useDefault:'1',//重置方式0 新密码  1 默认密码
@@ -476,7 +474,6 @@ import { encrypt } from "@/utils/jsencrypt";
             //重置密码
             ConfirmPassWord()
             {
-                console.log("打印重置密码",this.editPassWordData);
                 if(this.editPassWordData.useDefault=='1')
                 {
                     //默认密码
@@ -486,7 +483,6 @@ import { encrypt } from "@/utils/jsencrypt";
                     };
                     this.loading=true;
                     this.$api.user.resetPassWord(param).then(res=>{
-                        console.log("重置密码结果",res);
                         this.loading=false;
                         if(res.code==200)
                         {
@@ -522,7 +518,6 @@ import { encrypt } from "@/utils/jsencrypt";
                     };
                     this.loading=true;
                     this.$api.user.resetPassWord(param).then(res=>{
-                        console.log("重置密码结果",res);
                         this.loading=false;
                         if(res.code==200)
                         {
@@ -559,7 +554,6 @@ import { encrypt } from "@/utils/jsencrypt";
                 let ids=[];
                 let status=1;//启用
                 this.multipleSelection.map(item=>ids.push(item.id));
-                console.log("打印批量禁用ids",ids);
                 if(this.statusPage=='disabledUse')
                 {
                     status=1;//启用
@@ -709,7 +703,6 @@ import { encrypt } from "@/utils/jsencrypt";
             AddTeach(isEdit,item)
             {
                 this.isEdit = isEdit;
-                console.log("打印item",item);
                 if(this.isEdit=='edit')
                 {
                     this.editTeachData={
@@ -746,7 +739,6 @@ import { encrypt } from "@/utils/jsencrypt";
             //删除
             DeleteTeach(item)
             {
-                console.log("打印item",item);
                 this.$confirm(`确定要删除   ${item.teacherName}(${item.userAccount})吗?`, '提示', {
                     confirmButtonText: '确定',
                     cancelButtonText: '取消',
@@ -782,7 +774,6 @@ import { encrypt } from "@/utils/jsencrypt";
             //禁用
             DisabledTeach(item)
             {
-                console.log("item",item);
                 
                 // 状态(正式用户0=禁用 1=启用 临时用户代表0-关闭 1-开启)
                 if(this.statusPage=='disabledUse'){
@@ -921,7 +912,6 @@ import { encrypt } from "@/utils/jsencrypt";
                     this.resetPageData();
                     this.teacherList=[];
                     this.listTeacherUser(this.statusPage);
-                    console.log('上传成功:', response.data);
                     // 处理成功逻辑
                 })
                 .catch(error => {