|
@@ -0,0 +1,895 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <!-- 查看分析页。路由固定为 /teachingHomework/analysis,不使用地址栏参数 -->
|
|
|
|
|
+ <div class="homework_analysis" v-if="homework">
|
|
|
|
|
+ <div class="answer_header">
|
|
|
|
|
+ <el-button class="back_btn" @click="goBack">
|
|
|
|
|
+ <i class="iconfont icon_return"></i>返回
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <div class="header_main">
|
|
|
|
|
+ <div class="title_row">
|
|
|
|
|
+ <span class="subject_badge" :style="{ background: subjectColor }">{{ homework.subjectName }}</span>
|
|
|
|
|
+ <span class="task_title">{{ taskTitle }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="analysis_body">
|
|
|
|
|
+ <div class="side_nav">
|
|
|
|
|
+ <button
|
|
|
|
|
+ v-for="item in navList"
|
|
|
|
|
+ :key="item.key"
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ class="mm_btn"
|
|
|
|
|
+ :class="{ active: activeNav === item.key }"
|
|
|
|
|
+ @click="scrollTo(item.key)">
|
|
|
|
|
+ {{ item.label }}
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="main_scroll" ref="mainScroll" @scroll="onScroll">
|
|
|
|
|
+ <!-- 作业概况 -->
|
|
|
|
|
+ <section class="card" ref="overview">
|
|
|
|
|
+ <div class="card_title">作业概况</div>
|
|
|
|
|
+ <div class="metric_row">
|
|
|
|
|
+ <div class="metric" v-for="item in overviewList" :key="item.key">
|
|
|
|
|
+ <span class="metric_icon" :class="item.iconClass">
|
|
|
|
|
+ <i v-if="item.icon" :class="item.icon"></i>
|
|
|
|
|
+ <span v-else>%</span>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <div class="metric_text">
|
|
|
|
|
+ <div class="metric_label">{{ item.label }}</div>
|
|
|
|
|
+ <div class="metric_value">{{ item.value }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </section>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 小题分析图 + 选中题详情 -->
|
|
|
|
|
+ <section class="card" ref="qChart">
|
|
|
|
|
+ <div class="card_head">
|
|
|
|
|
+ <div class="card_title">小题分析图</div>
|
|
|
|
|
+ <div class="echart_type">
|
|
|
|
|
+ <span
|
|
|
|
|
+ class="chart_icon_item"
|
|
|
|
|
+ v-for="item in chartTypeList"
|
|
|
|
|
+ :key="item.value"
|
|
|
|
|
+ :class="qChartType === item.value ? `${item.value}_cur` : item.value"
|
|
|
|
|
+ @click="qChartType = item.value">
|
|
|
|
|
+ {{ item.label }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <BarChart
|
|
|
|
|
+ v-if="qChartType === 'vertical_bar'"
|
|
|
|
|
+ :datax="questionDatax"
|
|
|
|
|
+ :datay="questionDatay"
|
|
|
|
|
+ typeName="得分率"
|
|
|
|
|
+ unit="%"
|
|
|
|
|
+ :gridLeft="0"
|
|
|
|
|
+ :gridRight="0"
|
|
|
|
|
+ :isClick="true"
|
|
|
|
|
+ @HandleChartClick="onQuestionBarClick" />
|
|
|
|
|
+ <RadarCharts
|
|
|
|
|
+ v-else
|
|
|
|
|
+ :data="questionRadarData"
|
|
|
|
|
+ :showCheckBox="false"
|
|
|
|
|
+ :showLegend="false" />
|
|
|
|
|
+
|
|
|
|
|
+ <div class="question_detail" v-if="selectedQuestion">
|
|
|
|
|
+ <div class="detail_left">
|
|
|
|
|
+ <div class="detail_head">
|
|
|
|
|
+ <span class="detail_title">{{ selectedQuestion.name }} 题干</span>
|
|
|
|
|
+ <el-button class="ai_btn" @click="handleAiExplain">AI讲解</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="knowledge_tags">
|
|
|
|
|
+ <span class="knowledge_tag">知识点:{{ selectedQuestion.knowledge }}</span>
|
|
|
|
|
+ <span
|
|
|
|
|
+ class="knowledge_tag"
|
|
|
|
|
+ v-for="(tag, idx) in selectedQuestion.extraTags"
|
|
|
|
|
+ :key="idx">
|
|
|
|
|
+ {{ tag }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="stem_text">{{ selectedQuestion.stem }}</div>
|
|
|
|
|
+ <div class="option_list" v-if="selectedQuestion.options.length">
|
|
|
|
|
+ <div class="option_item" v-for="opt in selectedQuestion.options" :key="opt.key">
|
|
|
|
|
+ {{ opt.key }}. {{ opt.text }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="detail_right">
|
|
|
|
|
+ <div class="detail_tabs">
|
|
|
|
|
+ <span
|
|
|
|
|
+ class="detail_tab"
|
|
|
|
|
+ :class="{ active: detailTab === 'answer' }"
|
|
|
|
|
+ @click="detailTab = 'answer'">
|
|
|
|
|
+ 我的作答
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <span
|
|
|
|
|
+ class="detail_tab"
|
|
|
|
|
+ :class="{ active: detailTab === 'analysis' }"
|
|
|
|
|
+ @click="detailTab = 'analysis'">
|
|
|
|
|
+ 答案解析
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="score_row">
|
|
|
|
|
+ <span>得分:{{ selectedQuestion.score }}</span>
|
|
|
|
|
+ <span>得分率:{{ selectedQuestion.rate }}%</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-if="detailTab === 'answer'" class="answer_panel">
|
|
|
|
|
+ <div class="paper_box">
|
|
|
|
|
+ <div class="paper_title">学生作答</div>
|
|
|
|
|
+ <div class="paper_line" v-for="n in 6" :key="n"></div>
|
|
|
|
|
+ <div class="paper_choice" v-if="selectedQuestion.userChoice">
|
|
|
|
|
+ 我的选择:{{ selectedQuestion.userChoice }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-else class="analysis_panel">{{ selectedQuestion.analysis }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </section>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 小题分析表 -->
|
|
|
|
|
+ <section class="card" ref="qTable">
|
|
|
|
|
+ <div class="card_head">
|
|
|
|
|
+ <div class="card_title">小题分析表</div>
|
|
|
|
|
+ <el-button class="export_btn" @click="exportQuestionTable">导出Excel</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-table :data="questionPage" class="analysis_table">
|
|
|
|
|
+ <el-table-column prop="name" label="小题名称" min-width="120" />
|
|
|
|
|
+ <el-table-column prop="typeName" label="题型" min-width="100" />
|
|
|
|
|
+ <el-table-column prop="knowledge" label="知识点" min-width="180" />
|
|
|
|
|
+ <el-table-column prop="score" label="得分" min-width="80" />
|
|
|
|
|
+ <el-table-column label="得分率" min-width="100">
|
|
|
|
|
+ <template slot-scope="scope">{{ scope.row.rate }}%</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ <div class="pager_row" v-if="questionRows.length">
|
|
|
|
|
+ <el-pagination
|
|
|
|
|
+ background
|
|
|
|
|
+ layout="prev, pager, next"
|
|
|
|
|
+ :current-page.sync="questionPageNum"
|
|
|
|
|
+ :page-size="pageSize"
|
|
|
|
|
+ :total="questionRows.length" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </section>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 知识点分析图 -->
|
|
|
|
|
+ <section class="card" ref="kChart">
|
|
|
|
|
+ <div class="card_head">
|
|
|
|
|
+ <div class="card_title">知识点分析图</div>
|
|
|
|
|
+ <div class="echart_type">
|
|
|
|
|
+ <span
|
|
|
|
|
+ class="chart_icon_item"
|
|
|
|
|
+ v-for="item in chartTypeList"
|
|
|
|
|
+ :key="item.value"
|
|
|
|
|
+ :class="kChartType === item.value ? `${item.value}_cur` : item.value"
|
|
|
|
|
+ @click="kChartType = item.value">
|
|
|
|
|
+ {{ item.label }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <BarChart
|
|
|
|
|
+ v-if="kChartType === 'vertical_bar'"
|
|
|
|
|
+ :datax="knowledgeDatax"
|
|
|
|
|
+ :datay="knowledgeDatay"
|
|
|
|
|
+ typeName="得分率"
|
|
|
|
|
+ unit="%"
|
|
|
|
|
+ :gridLeft="0"
|
|
|
|
|
+ :gridRight="0" />
|
|
|
|
|
+ <RadarCharts
|
|
|
|
|
+ v-else
|
|
|
|
|
+ :data="knowledgeRadarData"
|
|
|
|
|
+ :showCheckBox="false"
|
|
|
|
|
+ :showLegend="false" />
|
|
|
|
|
+ </section>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 知识点分析表 -->
|
|
|
|
|
+ <section class="card" ref="kTable">
|
|
|
|
|
+ <div class="card_head">
|
|
|
|
|
+ <div class="card_title">知识点分析表</div>
|
|
|
|
|
+ <el-button class="export_btn" @click="exportKnowledgeTable">导出Excel</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-table :data="knowledgePage" class="analysis_table">
|
|
|
|
|
+ <el-table-column prop="name" label="知识点" min-width="200" />
|
|
|
|
|
+ <el-table-column prop="questions" label="包含小题" min-width="180" />
|
|
|
|
|
+ <el-table-column prop="score" label="得分" min-width="80" />
|
|
|
|
|
+ <el-table-column label="得分率" min-width="100">
|
|
|
|
|
+ <template slot-scope="scope">{{ scope.row.rate }}%</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ <div class="pager_row" v-if="knowledgeRows.length">
|
|
|
|
|
+ <el-pagination
|
|
|
|
|
+ background
|
|
|
|
|
+ layout="prev, pager, next"
|
|
|
|
|
+ :current-page.sync="knowledgePageNum"
|
|
|
|
|
+ :page-size="pageSize"
|
|
|
|
|
+ :total="knowledgeRows.length" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </section>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 学情总结 -->
|
|
|
|
|
+ <section class="card" ref="summary">
|
|
|
|
|
+ <div class="card_title">学情总结</div>
|
|
|
|
|
+ <div class="summary_block">
|
|
|
|
|
+ <span class="summary_label strength">优势</span>
|
|
|
|
|
+ <span class="summary_text">{{ summary.strength }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="summary_block">
|
|
|
|
|
+ <span class="summary_label weak">薄弱点</span>
|
|
|
|
|
+ <span class="summary_text">{{ summary.weakness }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="summary_block advice_block">
|
|
|
|
|
+ <span class="summary_label advice">学习建议</span>
|
|
|
|
|
+ <div class="summary_text">
|
|
|
|
|
+ <div v-for="(item, idx) in summary.advice" :key="idx">{{ idx + 1 }}. {{ item }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </section>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+import BarChart from '@/views/analysisReport/components/dCharts/barChart';
|
|
|
|
|
+import RadarCharts from '@/views/analysisReport/components/dCharts/radarCharts';
|
|
|
|
|
+
|
|
|
|
|
+const STORAGE_KEY = 'teachingHomeworkAnalysis';
|
|
|
|
|
+
|
|
|
|
|
+const OPTIONS = [
|
|
|
|
|
+ { key: 'A', text: '考古发现可以直接替代文献记载' },
|
|
|
|
|
+ { key: 'B', text: '出土文物能够为历史研究提供实物证据' },
|
|
|
|
|
+ { key: 'C', text: '考古结论一经形成便不再修正' },
|
|
|
|
|
+ { key: 'D', text: '文献记载与考古发现必然完全一致' },
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
|
|
+const EXTRA_TAGS = ['分析体裁特征和表现手法', '阅读理解'];
|
|
|
|
|
+
|
|
|
|
|
+function buildQuestionRows() {
|
|
|
|
|
+ const knowledgePool = ['作家作品', '分析体裁特征和表现手法', '阅读理解', '文言文阅读', '古诗词鉴赏', '语言表达'];
|
|
|
|
|
+ const rates = [96, 29, 88, 72, 65, 91, 54, 80, 43, 77];
|
|
|
|
|
+ const rows = [];
|
|
|
|
|
+ for (let i = 1; i <= 50; i += 1) {
|
|
|
|
|
+ const rate = rates[(i - 1) % rates.length];
|
|
|
|
|
+ const isChoice = i % 5 !== 0;
|
|
|
|
|
+ rows.push({
|
|
|
|
|
+ id: i,
|
|
|
|
|
+ name: `第${i}题`,
|
|
|
|
|
+ typeName: isChoice ? '选择题' : '解答题',
|
|
|
|
|
+ knowledge: knowledgePool[i % knowledgePool.length],
|
|
|
|
|
+ extraTags: EXTRA_TAGS,
|
|
|
|
|
+ score: ((rate / 100) * 5).toFixed(1),
|
|
|
|
|
+ rate,
|
|
|
|
|
+ stem: `下列关于考古发现与历史研究的表述,正确的一项是( )`,
|
|
|
|
|
+ options: isChoice ? OPTIONS : [],
|
|
|
|
|
+ userChoice: isChoice ? 'A' : '',
|
|
|
|
|
+ analysis: '本题考查对考古与历史研究关系的理解。出土文物可以为历史研究提供实物证据,正确答案为 B。',
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ return rows;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function buildKnowledgeRows() {
|
|
|
|
|
+ const names = ['作家作品', '分析体裁特征和表现手法', '阅读理解', '文言文实词', '古诗词鉴赏', '病句辨析', '语言表达', '名句默写', '现代文阅读', '作文立意'];
|
|
|
|
|
+ const rows = [];
|
|
|
|
|
+ for (let i = 0; i < 40; i += 1) {
|
|
|
|
|
+ const rate = 40 + ((i * 13) % 55);
|
|
|
|
|
+ const base = names[i % names.length];
|
|
|
|
|
+ const name = i >= names.length ? `${base}(${Math.floor(i / names.length) + 1})` : base;
|
|
|
|
|
+ rows.push({
|
|
|
|
|
+ id: i + 1,
|
|
|
|
|
+ name,
|
|
|
|
|
+ questions: `第${(i % 10) + 1}题、第${(i % 10) + 2}题`,
|
|
|
|
|
+ score: (rate / 20).toFixed(1),
|
|
|
|
|
+ rate,
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ return rows;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export default {
|
|
|
|
|
+ name: 'TeachingHomeworkAnalysis',
|
|
|
|
|
+ components: {
|
|
|
|
|
+ BarChart,
|
|
|
|
|
+ RadarCharts,
|
|
|
|
|
+ },
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ homework: null,
|
|
|
|
|
+ activeNav: 'overview',
|
|
|
|
|
+ scrollLock: false,
|
|
|
|
|
+ scrollTimer: null,
|
|
|
|
|
+ navList: [
|
|
|
|
|
+ { key: 'overview', label: '作业概况' },
|
|
|
|
|
+ { key: 'qChart', label: '小题分析图' },
|
|
|
|
|
+ { key: 'qTable', label: '小题分析表' },
|
|
|
|
|
+ { key: 'kChart', label: '知识点分析图' },
|
|
|
|
|
+ { key: 'kTable', label: '知识点分析表' },
|
|
|
|
|
+ { key: 'summary', label: '学情总结' },
|
|
|
|
|
+ ],
|
|
|
|
|
+ overview: {
|
|
|
|
|
+ fullScore: 100,
|
|
|
|
|
+ score: 65,
|
|
|
|
|
+ rate: '63.26%',
|
|
|
|
|
+ correctCount: 9,
|
|
|
|
|
+ questionCount: 65,
|
|
|
|
|
+ },
|
|
|
|
|
+ questionRows: buildQuestionRows(),
|
|
|
|
|
+ knowledgeRows: buildKnowledgeRows(),
|
|
|
|
|
+ qChartType: 'vertical_bar',
|
|
|
|
|
+ kChartType: 'vertical_bar',
|
|
|
|
|
+ chartTypeList: [
|
|
|
|
|
+ { label: '柱状图', value: 'vertical_bar' },
|
|
|
|
|
+ { label: '雷达图', value: 'radar_chart' },
|
|
|
|
|
+ ],
|
|
|
|
|
+ selectedIndex: 1,
|
|
|
|
|
+ detailTab: 'answer',
|
|
|
|
|
+ questionPageNum: 1,
|
|
|
|
|
+ knowledgePageNum: 1,
|
|
|
|
|
+ pageSize: 5,
|
|
|
|
|
+ summary: {
|
|
|
|
|
+ strength: '本次作业总体得分稳定,基础题掌握较好,阅读理解类题目得分率较高。',
|
|
|
|
|
+ weakness: '第2题及部分文言、表达类小题得分偏低,知识点迁移还不够熟练。',
|
|
|
|
|
+ advice: [
|
|
|
|
|
+ '针对失分小题回看题干和解析,整理同类题的答题步骤。',
|
|
|
|
|
+ '优先复习得分率较低的知识点,并完成对应小题的订正。',
|
|
|
|
|
+ '解答题注意分点作答,先写结论再补充依据。',
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ },
|
|
|
|
|
+ computed: {
|
|
|
|
|
+ taskTitle() {
|
|
|
|
|
+ if (!this.homework) return '';
|
|
|
|
|
+ const name = this.homework.homeworkName || '';
|
|
|
|
|
+ const className = this.homework.className || '';
|
|
|
|
|
+ if (!className || name.indexOf(className) !== -1) return name;
|
|
|
|
|
+ return `${name} ${className}`;
|
|
|
|
|
+ },
|
|
|
|
|
+ subjectColor() {
|
|
|
|
|
+ const name = (this.homework && this.homework.subjectName) || '';
|
|
|
|
|
+ return this.$global.getSubjectsName(name, null, 'color');
|
|
|
|
|
+ },
|
|
|
|
|
+ questionCount() {
|
|
|
|
|
+ if (this.homework && this.homework.questionNum != null) return this.homework.questionNum;
|
|
|
|
|
+ return this.overview.questionCount;
|
|
|
|
|
+ },
|
|
|
|
|
+ overviewList() {
|
|
|
|
|
+ return [
|
|
|
|
|
+ { key: 'full', label: '满分', value: this.overview.fullScore, icon: 'el-icon-document', iconClass: 'icon_blue' },
|
|
|
|
|
+ { key: 'score', label: '得分', value: this.overview.score, icon: 'el-icon-star-on', iconClass: 'icon_gold' },
|
|
|
|
|
+ { key: 'rate', label: '得分率', value: this.overview.rate, icon: '', iconClass: 'icon_teal' },
|
|
|
|
|
+ { key: 'correct', label: '答对题数', value: this.overview.correctCount, icon: 'el-icon-circle-check', iconClass: 'icon_green' },
|
|
|
|
|
+ { key: 'count', label: '试题数量', value: this.overview.questionCount, icon: 'el-icon-time', iconClass: 'icon_orange' },
|
|
|
|
|
+ ];
|
|
|
|
|
+ },
|
|
|
|
|
+ chartQuestions() {
|
|
|
|
|
+ return this.questionRows.slice(0, 10);
|
|
|
|
|
+ },
|
|
|
|
|
+ chartKnowledge() {
|
|
|
|
|
+ return this.knowledgeRows.slice(0, 8);
|
|
|
|
|
+ },
|
|
|
|
|
+ selectedQuestion() {
|
|
|
|
|
+ return this.chartQuestions[this.selectedIndex] || null;
|
|
|
|
|
+ },
|
|
|
|
|
+ questionPage() {
|
|
|
|
|
+ const start = (this.questionPageNum - 1) * this.pageSize;
|
|
|
|
|
+ return this.questionRows.slice(start, start + this.pageSize);
|
|
|
|
|
+ },
|
|
|
|
|
+ knowledgePage() {
|
|
|
|
|
+ const start = (this.knowledgePageNum - 1) * this.pageSize;
|
|
|
|
|
+ return this.knowledgeRows.slice(start, start + this.pageSize);
|
|
|
|
|
+ },
|
|
|
|
|
+ questionDatax() {
|
|
|
|
|
+ return this.chartQuestions.map(item => item.name);
|
|
|
|
|
+ },
|
|
|
|
|
+ questionDatay() {
|
|
|
|
|
+ return this.chartQuestions.map(item => item.rate);
|
|
|
|
|
+ },
|
|
|
|
|
+ questionRadarData() {
|
|
|
|
|
+ return [['group', '得分率'], ...this.chartQuestions.map(item => [item.name, item.rate])];
|
|
|
|
|
+ },
|
|
|
|
|
+ knowledgeDatax() {
|
|
|
|
|
+ return this.chartKnowledge.map(item => item.name);
|
|
|
|
|
+ },
|
|
|
|
|
+ knowledgeDatay() {
|
|
|
|
|
+ return this.chartKnowledge.map(item => item.rate);
|
|
|
|
|
+ },
|
|
|
|
|
+ knowledgeRadarData() {
|
|
|
|
|
+ return [['group', '得分率'], ...this.chartKnowledge.map(item => [item.name, item.rate])];
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ created() {
|
|
|
|
|
+ this.restoreState();
|
|
|
|
|
+ },
|
|
|
|
|
+ beforeDestroy() {
|
|
|
|
|
+ clearTimeout(this.scrollTimer);
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ restoreState() {
|
|
|
|
|
+ let cache = null;
|
|
|
|
|
+ try {
|
|
|
|
|
+ cache = JSON.parse(sessionStorage.getItem(STORAGE_KEY) || 'null');
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ cache = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!cache || !cache.homework) {
|
|
|
|
|
+ this.$router.replace('/teachingHomework/list');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.homework = cache.homework;
|
|
|
|
|
+ },
|
|
|
|
|
+ scrollTo(key) {
|
|
|
|
|
+ const el = this.$refs[key];
|
|
|
|
|
+ const box = this.$refs.mainScroll;
|
|
|
|
|
+ if (!el || !box) return;
|
|
|
|
|
+ this.activeNav = key;
|
|
|
|
|
+ this.scrollLock = true;
|
|
|
|
|
+ const top = el.getBoundingClientRect().top - box.getBoundingClientRect().top + box.scrollTop;
|
|
|
|
|
+ box.scrollTo({ top, behavior: 'smooth' });
|
|
|
|
|
+ clearTimeout(this.scrollTimer);
|
|
|
|
|
+ const start = Date.now();
|
|
|
|
|
+ const finish = () => {
|
|
|
|
|
+ const reached = Math.abs(box.scrollTop - top) < 2;
|
|
|
|
|
+ const atBottom = box.scrollTop + box.clientHeight >= box.scrollHeight - 2;
|
|
|
|
|
+ if (reached || atBottom || Date.now() - start > 1200) {
|
|
|
|
|
+ this.scrollLock = false;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.scrollTimer = setTimeout(finish, 50);
|
|
|
|
|
+ };
|
|
|
|
|
+ this.scrollTimer = setTimeout(finish, 50);
|
|
|
|
|
+ },
|
|
|
|
|
+ onScroll() {
|
|
|
|
|
+ if (this.scrollLock) return;
|
|
|
|
|
+ const box = this.$refs.mainScroll;
|
|
|
|
|
+ if (!box) return;
|
|
|
|
|
+ const scrollTop = box.scrollTop + 12;
|
|
|
|
|
+ let current = this.navList[0].key;
|
|
|
|
|
+ this.navList.forEach(item => {
|
|
|
|
|
+ const el = this.$refs[item.key];
|
|
|
|
|
+ if (el && el.offsetTop <= scrollTop) current = item.key;
|
|
|
|
|
+ });
|
|
|
|
|
+ this.activeNav = current;
|
|
|
|
|
+ },
|
|
|
|
|
+ onQuestionBarClick(index) {
|
|
|
|
|
+ if (index == null || index < 0) return;
|
|
|
|
|
+ this.selectedIndex = index;
|
|
|
|
|
+ this.detailTab = 'answer';
|
|
|
|
|
+ },
|
|
|
|
|
+ downloadExcel(filename, columns, rows) {
|
|
|
|
|
+ const head = columns.map(col => `<th>${col.label}</th>`).join('');
|
|
|
|
|
+ const body = rows.map(row => {
|
|
|
|
|
+ const tds = columns.map(col => `<td>${row[col.prop] == null ? '' : row[col.prop]}</td>`).join('');
|
|
|
|
|
+ return `<tr>${tds}</tr>`;
|
|
|
|
|
+ }).join('');
|
|
|
|
|
+ const html = `<html><head><meta charset="utf-8"></head><body><table border="1"><tr>${head}</tr>${body}</table></body></html>`;
|
|
|
|
|
+ const blob = new Blob(['\ufeff', html], { type: 'application/vnd.ms-excel' });
|
|
|
|
|
+ const url = URL.createObjectURL(blob);
|
|
|
|
|
+ const link = document.createElement('a');
|
|
|
|
|
+ link.href = url;
|
|
|
|
|
+ link.download = filename;
|
|
|
|
|
+ link.click();
|
|
|
|
|
+ URL.revokeObjectURL(url);
|
|
|
|
|
+ },
|
|
|
|
|
+ exportQuestionTable() {
|
|
|
|
|
+ this.downloadExcel('小题分析表.xls', [
|
|
|
|
|
+ { label: '小题名称', prop: 'name' },
|
|
|
|
|
+ { label: '题型', prop: 'typeName' },
|
|
|
|
|
+ { label: '知识点', prop: 'knowledge' },
|
|
|
|
|
+ { label: '得分', prop: 'score' },
|
|
|
|
|
+ { label: '得分率', prop: 'rateText' },
|
|
|
|
|
+ ], this.questionRows.map(item => ({ ...item, rateText: `${item.rate}%` })));
|
|
|
|
|
+ },
|
|
|
|
|
+ exportKnowledgeTable() {
|
|
|
|
|
+ this.downloadExcel('知识点分析表.xls', [
|
|
|
|
|
+ { label: '知识点', prop: 'name' },
|
|
|
|
|
+ { label: '包含小题', prop: 'questions' },
|
|
|
|
|
+ { label: '得分', prop: 'score' },
|
|
|
|
|
+ { label: '得分率', prop: 'rateText' },
|
|
|
|
|
+ ], this.knowledgeRows.map(item => ({ ...item, rateText: `${item.rate}%` })));
|
|
|
|
|
+ },
|
|
|
|
|
+ handleAiExplain() {
|
|
|
|
|
+ this.$message.info('AI讲解待对接');
|
|
|
|
|
+ },
|
|
|
|
|
+ goBack() {
|
|
|
|
|
+ this.$router.push('/teachingHomework/list');
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+};
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
|
+.homework_analysis {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ min-height: 0;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+
|
|
|
|
|
+ .answer_header {
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ gap: 16px;
|
|
|
|
|
+ padding: 10px;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border-radius: 10px;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+
|
|
|
|
|
+ .back_btn {
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ height: 36px;
|
|
|
|
|
+ padding: 0 12px;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border: 1px solid #DCDFE6;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+
|
|
|
|
|
+ :deep(span) {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 2px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .header_main {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+
|
|
|
|
|
+ .title_row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
|
+ margin-top: 6px;
|
|
|
|
|
+
|
|
|
|
|
+ .subject_badge {
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ min-width: 40px;
|
|
|
|
|
+ height: 25px;
|
|
|
|
|
+ padding: 0 4px;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .task_title {
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ font-weight: 550;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .meta_row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: 16px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .analysis_body {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-height: 0;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ margin-top: 10px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* 与分析报告左侧导航一致:白底圆角卡片 + mm_btn */
|
|
|
|
|
+ .side_nav {
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ width: 170px;
|
|
|
|
|
+ height: fit-content;
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+ border-radius: 10px;
|
|
|
|
|
+ background-color: #fff;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+
|
|
|
|
|
+ .mm_btn + .mm_btn {
|
|
|
|
|
+ margin-top: 10px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .main_scroll {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .card {
|
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
|
+ padding: 16px;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border-radius: 10px;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+
|
|
|
|
|
+ .card_title {
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ font-weight: 550;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ line-height: 24px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .card_head {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .metric_row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ margin-top: 16px;
|
|
|
|
|
+
|
|
|
|
|
+ .metric {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ padding: 14px 12px;
|
|
|
|
|
+ background: #F7F9FC;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .metric_icon {
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ width: 36px;
|
|
|
|
|
+ height: 36px;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+
|
|
|
|
|
+ &.icon_blue { background: #409EFF; }
|
|
|
|
|
+ &.icon_gold { background: #E6A23C; }
|
|
|
|
|
+ &.icon_teal { background: #3BA8A0; font-size: 16px; font-weight: 550; }
|
|
|
|
|
+ &.icon_green { background: #67C23A; }
|
|
|
|
|
+ &.icon_orange { background: #F5A623; }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .metric_label {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+ line-height: 18px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .metric_value {
|
|
|
|
|
+ margin-top: 4px;
|
|
|
|
|
+ font-size: 22px;
|
|
|
|
|
+ font-weight: 550;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ line-height: 28px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .question_detail {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 16px;
|
|
|
|
|
+ margin-top: 8px;
|
|
|
|
|
+ padding-top: 16px;
|
|
|
|
|
+ border-top: 1px solid #EBEEF5;
|
|
|
|
|
+
|
|
|
|
|
+ .detail_left,
|
|
|
|
|
+ .detail_right {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .detail_head {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .detail_title {
|
|
|
|
|
+ font-size: 15px;
|
|
|
|
|
+ font-weight: 550;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .ai_btn {
|
|
|
|
|
+ height: 32px;
|
|
|
|
|
+ padding: 0 12px;
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ background: #847BE6;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+
|
|
|
|
|
+ &:hover,
|
|
|
|
|
+ &:focus {
|
|
|
|
|
+ background: #847BE6;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ opacity: 0.92;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .knowledge_tags {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+
|
|
|
|
|
+ .knowledge_tag {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ height: 28px;
|
|
|
|
|
+ padding: 0 10px;
|
|
|
|
|
+ border: 1px solid #409EFF;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ color: #409EFF;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .stem_text,
|
|
|
|
|
+ .option_item {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ line-height: 1.7;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .option_item {
|
|
|
|
|
+ margin-top: 6px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .detail_tabs {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 24px;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+
|
|
|
|
|
+ .detail_tab {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ height: 32px;
|
|
|
|
|
+ line-height: 32px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+
|
|
|
|
|
+ &.active {
|
|
|
|
|
+ color: #409EFF;
|
|
|
|
|
+ font-weight: 550;
|
|
|
|
|
+
|
|
|
|
|
+ &::after {
|
|
|
|
|
+ content: '';
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ left: 0;
|
|
|
|
|
+ right: 0;
|
|
|
|
|
+ bottom: 0;
|
|
|
|
|
+ height: 2px;
|
|
|
|
|
+ background: #409EFF;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .score_row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 16px;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #409EFF;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .paper_box {
|
|
|
|
|
+ min-height: 220px;
|
|
|
|
|
+ padding: 16px;
|
|
|
|
|
+ border: 1px solid #EBEEF5;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ background: #FAFBFC;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+
|
|
|
|
|
+ .paper_title {
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .paper_line {
|
|
|
|
|
+ height: 10px;
|
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
|
+ background: #EEF1F6;
|
|
|
|
|
+ border-radius: 2px;
|
|
|
|
|
+
|
|
|
|
|
+ &:nth-child(odd) {
|
|
|
|
|
+ width: 92%;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &:nth-child(even) {
|
|
|
|
|
+ width: 70%;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .paper_choice {
|
|
|
|
|
+ margin-top: 8px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .analysis_panel {
|
|
|
|
|
+ min-height: 220px;
|
|
|
|
|
+ padding: 16px;
|
|
|
|
|
+ border: 1px solid #EBEEF5;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ background: #FAFBFC;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ line-height: 1.7;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .export_btn {
|
|
|
|
|
+ height: 32px;
|
|
|
|
|
+ padding: 0 12px;
|
|
|
|
|
+ color: #2E64FA;
|
|
|
|
|
+ border-color: #2E64FA;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .analysis_table {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+
|
|
|
|
|
+ :deep(th) {
|
|
|
|
|
+ background: #F5F7FA;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ font-weight: 550;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .pager_row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+ margin-top: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .summary_block {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ margin-top: 14px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ line-height: 1.7;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+
|
|
|
|
|
+ .summary_label {
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ font-weight: 550;
|
|
|
|
|
+
|
|
|
|
|
+ &.strength { color: #67C23A; }
|
|
|
|
|
+ &.weak { color: #F56C6C; }
|
|
|
|
|
+ &.advice { color: #409EFF; }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|