ソースを参照

个人画像-答题卡-精准提升试题

吴朋磊 3 ヶ月 前
コミット
acda2cb677

+ 6 - 2
src/http/api/personalProfile.js

@@ -1,4 +1,4 @@
-import { get, post } from "../common/http";
+import { get, post,downLoadByBlob} from "../common/http";
 import base from "../common/base";
 
 const report = {
@@ -21,7 +21,11 @@ const report = {
     // 推送试题列表
     pushQuestionList(data){
         return get(`${base.prefix}/api/v1/studentPinpoint/pushQuestionList`,data);
-    }
+    },
+    // 导出精准提升试题
+    downloadPushQuestion(data){
+        return downLoadByBlob(`${base.prefix}/api/v1/studentPinpoint/downloadPushQuestion`,data);
+    },
 };
 
 export default report;

+ 141 - 0
src/views/analysisReport/personalProfile/Download.vue

@@ -0,0 +1,141 @@
+<template>
+    <el-dialog title="导出精准提升试题" class="mm_dialog" :visible="visible" width="360px" @close="handleClose">
+        <div class="box">
+            <div class="item" :class="{ isAnswer: isAnswer === 1 }" @click="isAnswer = 1">
+                有答案解析
+                <i class="iconfont icon_Check" v-if="isAnswer === 1"></i>
+            </div>
+            <div class="item" :class="{ isAnswer: isAnswer === 0 }" @click="isAnswer = 0">
+                无答案解析
+                <i class="iconfont icon_Check" v-if="isAnswer === 0"></i>
+            </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 { 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
+        },
+        knowledgeId: {
+            type: [String, Number],
+            required: true
+        },
+    },
+
+    data() {
+        return {
+            isAnswer: 1
+        };
+    },
+
+    methods: {
+        handleClose() {
+            this.$emit('update:visible', false);
+        },
+
+        download() {
+            let downParams ={
+                examId: this.examId,
+                subjectCode: this.subjectCode,
+                knowledgeId: this.knowledgeId, 
+                isAnswer: this.isAnswer,
+            }
+            const loadingInstance = CreateLoadingInstance()
+            this.$api.personalProfile.downloadPushQuestion(downParams).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)
+                this.handleClose();
+            }).catch(error => {
+                this.$message.error('下载失败,请稍后重试');
+            }).finally(() => {
+                loadingInstance.close();
+                this.$store.commit("question/SET_VARIANTION", -1); 
+            });
+        }
+    }
+};
+</script>
+
+<style scoped lang="scss">
+.box {
+    display: flex;
+    justify-content: space-between;
+    .item {
+        width: 150px;
+        height: 50px;
+        line-height: 50px;
+        text-align: center;
+        font-size: 14px;
+        color: #909399;
+        background: #EDEFF6;
+        border-radius: 4px;
+        cursor: pointer;
+
+        &.isAnswer {
+            color: #2B65FF;
+            background: rgba(46,100,250,0.1);
+            border: 1px solid #2E64FA;
+            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;
+            }
+
+            .iconfont {
+                position: absolute;
+                right: 0;
+                bottom: -18px;
+                font-size: 16px;
+                color: #ffffff;
+                z-index: 1;
+            }
+        }
+    }
+}
+:deep(.el-dialog__headerbtn){
+    top: 13px;
+    font-size: 20px;
+}
+:deep(.el-dialog__title){
+    font-weight: 500;
+    font-size: 16px;
+    color: #303133;
+}
+:deep(.el-button){
+    width: 68px;
+    height: 36px;
+    padding: 0;
+    font-size: 14px;
+    font-weight: 500;
+    margin-left: 20px;
+}
+</style>

+ 4 - 2
src/views/analysisReport/personalProfile/HistoricalChangeChart.vue

@@ -478,11 +478,13 @@ export default {
         justify-content: center;
         align-items: center;
         width: 100%;
-        height: 290px;
+        height: 220px;
         font-size: 14px;
         color: #999999;
-        background-color: #fafafa;
         border-radius: 4px;
+        span{
+          margin-top: 11%;
+        }
     }
 }
 </style>

+ 4 - 2
src/views/analysisReport/personalProfile/KnowledgeTrack.vue

@@ -105,7 +105,7 @@ export default {
   //暂无数据
   .noData {
     position: relative;
-    height: 250px;
+    height: 220px;
 
     /* 暂无数据样式 */
     .no_data {
@@ -121,8 +121,10 @@ export default {
       display: flex;
       align-items: center;
       justify-content: center;
-      background-color: #fafafa;
       border-radius: 8px;
+      span{
+        margin-top: 11%;
+      }
     }
   }
 }  

+ 42 - 9
src/views/analysisReport/personalProfile/MyGradeHistory.vue

@@ -18,7 +18,7 @@
         </el-table-column>
         <el-table-column label="操作" width="120" align="center">
           <template slot-scope="scope">
-            <el-button size="mini" type="text" @click="viewDetails(scope.row)">查看答题卡</el-button>
+            <el-button  type="text" @click="viewDetails(scope.row)">查看答题卡</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -31,13 +31,25 @@
         <span>暂无数据</span>
       </div>
     </div>
-  </div>
 
+      <!-- 学生答题卡预览组件 -->
+    <StudentPaper
+      v-model="showStudentPaperDialog"
+      :paperInfo="paperInfo"
+      :currentPageIndex="currentPageIndex"
+      :pageTitle="paperTitle"
+    ></StudentPaper>
+  </div>
 
 </template>
 
 <script>
+import StudentPaper from '@/components/StudentPaper.vue' //学生答题卡预览组件
+import { mapGetters } from 'vuex'
 export default {
+  components: {
+    StudentPaper
+  },
   name: "MyGradeHistory",
   props: {
     mode: {
@@ -52,9 +64,27 @@ export default {
   },
   data() {
     return {
+      showStudentPaperDialog: false, //显示答题卡弹框
+      paperInfo: {},
+      paperTitle: '', //答题卡弹框标题
+      currentPageIndex: 0 //当前选中的答题卡第几页
     };
   },
   computed: {
+    ...mapGetters(['userInfo']),
+    pageName() {
+      return this.$store.state.report.examSelectItem.examName
+    }, //考试名称
+    reportParam() {
+      return {
+        examLevel: this.$store.state.report.filterObject.examLevel, //1-联考 2-单校
+        contrastExamIds: this.$store.state.report.filterObject.contrastExamIds, //多次考试任务对比ID,不包含当前任务ID
+        examId: this.$store.state.report.filterObject.examId, //考试id
+        subjectCode: this.$store.state.report.filterObject.subjectCode, //科目code
+        subjectGroupType: this.$store.state.report.filterObject.subjectGroupType, //是否为组合科目 1为组合科目 0为非组合科目
+        isTotal: this.$store.state.report.filterObject.isTotal //是否为总分科目 1为总分 0为非总分
+      }
+    }, //分析报告公共参数变量
     // 处理数据,映射接口字段到表格字段
     tableData() {
       return this.gradeHistoryData.map(item => ({
@@ -65,7 +95,9 @@ export default {
         classRank: item.classRank,
         gradeRank: item.gradeRank,
         standardScore: item.standardScore,
-        scoreRate: item.scoreRate
+        scoreRate: item.scoreRate,
+        examId: item.examId, //考试id
+        subjectId: item.subjectId, //科目id
       }));
     },
     // 表头样式
@@ -92,10 +124,9 @@ export default {
   },
   methods: {
     viewDetails(row) {
-      this.$message({
-        message: `查看${row.examName}的详情`,
-        type: 'info'
-      });
+      this.paperTitle = `${row.examName}_${this.userInfo.userName}`
+      this.paperInfo = { examId: this.reportParam.examId, subjectCode: this.reportParam.subjectCode }
+      this.showStudentPaperDialog = true
       // 实际项目中可以跳转到详情页面或展开详情
     },
   }
@@ -199,7 +230,7 @@ export default {
   // 暂无数据样式
   .noData {
     position: relative;
-    height: 250px;
+    height: 220px;
 
     /* 暂无数据样式 */
     .no_data {
@@ -215,8 +246,10 @@ export default {
       display: flex;
       align-items: center;
       justify-content: center;
-      background-color: #fafafa;
       border-radius: 8px;
+      span{
+        margin-top: 11%;
+      }
     }
   }
 }

+ 49 - 7
src/views/analysisReport/personalProfile/index.vue

@@ -39,6 +39,16 @@
          <knowledgePaps
             :knowledgeName="knowledgeName"
             v-loading="knowledgePapsLoading"
+            :knowledgData="knowledgData"
+            @export-knowledge-paps="exportKnowledgePaps"
+        />
+        <!-- 下载试题 -->
+        <Download 
+            ref="downloadRef" 
+            :visible.sync="visible"
+            :examId="portraitData.examId" 
+            :subjectCode="portraitData.subjectCode" 
+            :knowledgeId="knowledgeId" 
         />
     </div>
 </template>
@@ -48,6 +58,7 @@ import KnowledgeTrack from './KnowledgeTrack.vue'; //历次考试知识点追踪
 import zeroScoreKnowledge from './zeroScoreKnowledge.vue'; //零分知识点、高频错题知识点子组件
 import HistoricalChangeChart from './HistoricalChangeChart.vue'; //历次变化子组件
 import knowledgePaps from './knowledgePaps.vue'; //薄弱知识点精准提升子组件
+import Download from './Download.vue'; //下载组件
 
 export default {
     name: "PersonalProfile",
@@ -56,7 +67,8 @@ export default {
         KnowledgeTrack,
         zeroScoreKnowledge,
         HistoricalChangeChart,
-        knowledgePaps
+        knowledgePaps,
+        Download
     },
     computed: {
         portraitData() {
@@ -139,6 +151,8 @@ export default {
                 gradeList: [],//年级得分率
                 examName: [] // 考试名称
             },
+            // 薄弱知识点数据
+            knowledgData: [],
             // 我的历次成绩-加载状态
             historyloading: false,
             // 历次考试知识点追踪-加载状态
@@ -149,6 +163,8 @@ export default {
             historicalChangeLoading: false,
             // 薄弱知识点精准提升-加载状态
             knowledgePapsLoading: false,
+            // 下载组件显示状态
+            visible: false,
         };
     },
     watch: {
@@ -170,11 +186,13 @@ export default {
             // 加载状态-清空数据
             this.historyloading = true;
             // 历次考试知识点追踪-加载状态
-             this.knowledgeloading = false,
+             this.knowledgeloading = true;
             // 零分知识点、高频错题知识点-加载状态
-             this.zeroloading = false,
+             this.zeroloading = true;
+             // 薄弱知识点精准提升-加载状态
+             this.knowledgePapsLoading = true;
             // 历次变化图表-加载状态
-             this.historicalChangeLoading = false,
+            this.historicalChangeLoading = true;
             this.gradeHistoryData = [];
             let examParams = {
                 examId: this.portraitData.examId,
@@ -435,6 +453,7 @@ export default {
 
         // 推题列表
         pushQuestionData(){
+            this.knowledgData = [];
             let params = {
                 examId: this.portraitData.examId, //当前考试id
                 subjectCode: this.portraitData.subjectCode, //学科code
@@ -442,10 +461,23 @@ export default {
             };
             this.$api.personalProfile.pushQuestionList(params).then(res => {
                 if (res.code === 200) {
-                    let data = res.data || [];
-                    console.log(data);
+                    let data = res.data ;
+                    if (data) {
+                        // 加载状态-清空数据
+                        this.knowledgePapsLoading = false;
+                         // 更新推题列表数据
+                        this.knowledgData = data || [];
+                    }else{
+                        // 加载状态-清空数据
+                        this.knowledgePapsLoading = false;
+                         // 更新推题列表数据
+                        this.knowledgData =  [];
+                    }
                 } else {
-                   
+                   // 加载状态-清空数据
+                    this.knowledgePapsLoading = false;
+                    // 更新推题列表数据
+                    this.knowledgData =  [];
                 }
             });
         },
@@ -503,6 +535,16 @@ export default {
             // 点击知识点后,更新推题列表
             this.pushQuestionData();
         },
+
+        // 导出精准提升试题
+        exportKnowledgePaps() {
+            if(this.knowledgData.length === 0){
+                this.$message.warning('暂无知识点推题');
+                return
+            }
+            // 触发Download组件弹窗
+            this.visible = true;
+        }
     },
 }
 </script>

+ 48 - 78
src/views/analysisReport/personalProfile/knowledgePaps.vue

@@ -6,45 +6,47 @@
                 <span class="secondary_title" v-if="titleParts.knowledgeName">{{ titleParts.knowledgeName }} / </span>
                 <span class="main_title">薄弱知识点精准提升</span>
             </div>
-            <button class="export_btn">
+            <button class="export_btn" @click="$emit('export-knowledge-paps')">
                 <i class="iconfont icon_export"></i>
                 导出精准提升试题
             </button>
         </div>
 
-        <div class="data_container" v-if="knowledgePapsData.length > 0">
-            <div class="question_item" v-for="(item, index) in knowledgePapsData" :key="index">
+        <div class="data_container" v-if="knowledgData.length > 0">
+            <div class="question_item" v-for="(item, index) in knowledgData" :key="index">
+                <!-- 推题头部 -->
                 <div class="question_header">
                     <div class="question_meta">
                         <span class="question_num">{{ index + 1 }}</span>
                         <span class="question_type">题型类型:
-                            <i class="typeColor" style="color:#333333">{{ item.type }}</i>
+                            <i class="typeColor" style="color:#333333">{{ item.questionType }}</i>
                         </span>
                         <span class="question_difficulty">难度:
-                            <i class="typeColor" style="color:#fac858">{{ item.difficulty }}</i>
+                            <i class="typeColor" style="color:#fac858">{{ item.questionLevel }}</i>
                         </span>
                     </div>
                     <div class="question_source">来源:
-                        <i class="typeColor" style="color:#333333">{{ item.source }}</i>
+                        <i class="typeColor" style="color:#333333">{{ item.fromPaper }}</i>
                     </div>
                 </div>
+                <!-- 推题内容 -->
                 <div class="question_content">
-                    <p>{{ item.question }}</p>
-                    <div class="options">
-                        <div v-for="(option, optIndex) in item.options" :key="optIndex" class="option_item">
-                            <span class="option_letter">{{ String.fromCharCode(65 + optIndex) }}.</span>
-                            <span class="option_text">{{ option }}</span>
-                        </div>
-                    </div>
+                    <p v-html ='item.title || "" '></p>
                 </div>
+                <!-- 推题底部 -->
                 <div class="question_tags">
                     <span class="tags_label">知识点:</span>
-                    <span class="tag" v-for="(tag, tagIndex) in item.tags" :key="tagIndex">{{ tag }}</span>
+                    <span class="tag" v-for="(knowledge, index) in (item.knowledge || '').split('、')" :key="index" v-if="knowledge">
+                        {{ knowledge }}
+                    </span>
                 </div>
             </div>
         </div>
-        <div class="map_content" v-else>
-            <p>暂无数据</p>
+
+        <div 
+           class="module_chart no_content_data no_data"  
+           element-loading-background="#ffffff" v-else>
+          <span>暂无数据</span> 
         </div>
     </div>
 </template>
@@ -56,6 +58,11 @@ export default {
         knowledgeName: {
             type: String,
             default: ''
+        },
+        // 薄弱知识点精准提升数据
+        knowledgData: {
+            type: Array,
+            default: () => []
         }
     },
     computed: {
@@ -68,41 +75,21 @@ export default {
     },
     data() {
         return {
-            // 薄弱知识点精准提升数据
-            knowledgePapsData: []
         }
     },
     mounted() {
-        // 加载薄弱知识点精准提升数据
-        this.knowledgePapsData = [
-            {
-                type: "语言表达",
-                difficulty: "较易",
-                source: "大数据题库",
-                question: "下列各组词语中书写无误的一组是",
-                options: [
-                    "寻常巷陌 舞榭歌台 歌忱无忧 惊滔拍岸",
-                    "玉枕纱厨 故国神游 淡装浓抹 夜弦西边",
-                    "刎颈之交 玉簪螺髻 烟柳画桥 晓风残月",
-                    "良晨好景 吟赏烟霞 完璧归赵 神鸦社鼓"
-                ],
-                tags: ["古代诗歌阅读", "分析、鉴赏诗歌意境"]
-            },
-            {
-                type: "阅读理解",
-                difficulty: "中等",
-                source: "大数据题库",
-                question: "下列对文章内容的理解,不正确的一项是",
-                options: [
-                    "作者认为,学习的目的是为了提升自身修养,而不是为了追求功名利禄",
-                    "文章通过对比古今学者的学习态度,强调了脚踏实地的重要性",
-                    "作者主张学习应该广泛涉猎,不必专注于某一领域",
-                    "文章最后提出了“学以致用”的观点,认为学习应该服务于实践"
-                ],
-                tags: ["现代文阅读", "分析文章结构,归纳内容要点"]
-            }
-        ];
-    }
+    },
+    watch: {
+        // knowledgData: {
+        //     handler(newVal, oldVal) {
+        //         console.log(newVal);
+        //     },
+        //     deep: true
+        // }
+    },
+    methods: {
+        
+    },
 }
 </script>
 <style scoped lang="scss">
@@ -169,6 +156,7 @@ export default {
             }
         }
     }
+
     // 内容部分
     .question_item {
         background: #FFFFFF;
@@ -227,30 +215,7 @@ export default {
                 font-size: 14px;
                 color: #666666;
                 margin-bottom: 10px;
-                line-height: 1.5;
-            }
-
-            .options {
-                display: grid;
-                grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
-                gap: 10px;
-
-                .option_item {
-                    display: flex;
-                    align-items: flex-start;
-                    gap: 8px;
-                    font-size: 14px;
-                    color: #666;
-                    line-height: 1.5;
-
-                    .option_letter {
-                        min-width: 20px;
-                    }
-
-                    .option_text {
-                        flex: 1;
-                    }
-                }
+                line-height: 2; 
             }
         }
         // 试题底部
@@ -277,12 +242,17 @@ export default {
     .question_item:last-child {
         margin-bottom: 0;
     }
-    .map_content {
-        p {
-            margin-bottom: 5px;
-            text-align: center;
-            color: #999;
-            padding: 40px 0;
+
+    // 暂无数据
+    .no_data{
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        width: 100%;
+        height: 220px;
+        font-size: 14px;
+        span{
+            margin-top: 11%;
         }
     }
 }

+ 59 - 0
src/views/analysisReport/personalProfile/tools.js

@@ -0,0 +1,59 @@
+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: '正在处理...',
+    }, options || {});
+
+    return Loading.service(options);
+}

+ 12 - 6
src/views/analysisReport/personalProfile/zeroScoreKnowledge.vue

@@ -936,12 +936,14 @@ export default {
         color: #909399;
         text-align: center;
         width: 100%;
-        height: 100%;
+        height: 220px;
         display: flex;
         align-items: center;
         justify-content: center;
-        background-color: #fafafa;
         border-radius: 8px;
+        span{
+          margin-top:16%;
+        }
       }
 
     }
@@ -982,12 +984,14 @@ export default {
         color: #909399;
         text-align: center;
         width: 100%;
-        height: 100%;
+        height: 240px;
         display: flex;
         align-items: center;
         justify-content: center;
-        background-color: #fafafa;
         border-radius: 8px;
+        span{
+          margin-top: 35%;
+        }
       }
 
       .list_item {
@@ -1213,7 +1217,7 @@ export default {
   // 暂无数据样式
   .noData {
     position: relative;
-    height: 250px;
+    height: 220px;
 
     /* 暂无数据样式 */
     .no_data {
@@ -1229,8 +1233,10 @@ export default {
       display: flex;
       align-items: center;
       justify-content: center;
-      background-color: #fafafa;
       border-radius: 8px;
+      span{
+        margin-top: 11%;
+      }
     }
   }
 }