|
|
@@ -14,7 +14,7 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div class="analysis_body">
|
|
|
+ <div class="analysis_body" v-loading="loading">
|
|
|
<div class="side_nav">
|
|
|
<button
|
|
|
v-for="item in navList"
|
|
|
@@ -40,7 +40,7 @@
|
|
|
</div>
|
|
|
</section>
|
|
|
|
|
|
- <!-- 小题分析图 + 选中题详情 -->
|
|
|
+ <!-- 小题分析图(独立卡片) -->
|
|
|
<section class="card" ref="qChart">
|
|
|
<div class="card_head">
|
|
|
<div class="card_title">小题分析图</div>
|
|
|
@@ -55,99 +55,189 @@
|
|
|
</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 }}
|
|
|
+ <template v-if="questionRows.length">
|
|
|
+ <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" />
|
|
|
+ </template>
|
|
|
+ <div v-else-if="!loading" class="empty_tip no_content_data">
|
|
|
+ <span>暂无数据</span>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <!-- 选中小题:左题干 / 右我的作答,两张独立卡片 -->
|
|
|
+ <div class="question_detail_wrap" v-if="selectedQuestion">
|
|
|
+ <div class="card detail_card stem_card">
|
|
|
+ <div class="detail_head">
|
|
|
+ <span class="detail_title">第{{ selectedIndex + 1 }}题 {{ selectedQuestion.name }}</span>
|
|
|
+ <el-button class="ai_btn" @click="handleAiExplain">AI讲题</el-button>
|
|
|
+ </div>
|
|
|
+ <div class="knowledge_tags" v-if="selectedQuestion.knowledgeTags.length">
|
|
|
+ <span class="knowledge_label">知识点:</span>
|
|
|
+ <span
|
|
|
+ class="knowledge_tag"
|
|
|
+ v-for="(tag, idx) in selectedQuestion.knowledgeTags"
|
|
|
+ :key="idx"
|
|
|
+ :title="tag">
|
|
|
+ {{ formatKnowledgeTag(tag) }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="stem_scroll">
|
|
|
+ <!-- 大题题干 -->
|
|
|
+ <template v-if="selectedQuestion.title || selectedQuestion.options.length">
|
|
|
+ <div class="stem_text" v-if="selectedQuestion.title" v-html="selectedQuestion.title"></div>
|
|
|
+ <div class="option_list" v-if="selectedQuestion.options.length">
|
|
|
+ <div class="option_item" v-for="opt in selectedQuestion.options" :key="opt.key">
|
|
|
+ <span class="opt_key">{{ opt.key }}.</span>
|
|
|
+ <span class="opt_text" v-html="opt.text"></span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <!-- 子题 -->
|
|
|
+ <div
|
|
|
+ class="sub_question"
|
|
|
+ v-for="(sub, sIdx) in selectedQuestion.subQuestions"
|
|
|
+ :key="sub.id || sIdx">
|
|
|
+ <div class="sub_title" v-if="sub.title" v-html="sub.title"></div>
|
|
|
+ <!-- <div class="knowledge_tags" v-if="sub.knowledgeTags.length">
|
|
|
+ <span
|
|
|
+ class="knowledge_tag sm"
|
|
|
+ v-for="(tag, kIdx) in sub.knowledgeTags"
|
|
|
+ :key="kIdx"
|
|
|
+ :title="tag">
|
|
|
+ {{ formatKnowledgeTag(tag) }}
|
|
|
+ </span>
|
|
|
+ </div> -->
|
|
|
+ <div class="option_list" v-if="sub.options.length">
|
|
|
+ <div class="option_item" v-for="opt in sub.options" :key="opt.key">
|
|
|
+ <span class="opt_key">{{ opt.key }}.</span>
|
|
|
+ <span class="opt_text" v-html="opt.text"></span>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-if="!selectedQuestion.title && !selectedQuestion.options.length
|
|
|
+ && !selectedQuestion.subQuestions.length"
|
|
|
+ class="content_empty no_content_data">
|
|
|
+ <span>暂无题干数据</span>
|
|
|
+ </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'">
|
|
|
- 答案解析
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 右侧:tabs 独立卡片 + 作答内容卡片,上下间隔 10px -->
|
|
|
+ <div class="answer_col">
|
|
|
+ <div class="card detail_tabs_card">
|
|
|
+ <el-radio-group v-model="detailTab" class="detail_radio">
|
|
|
+ <el-radio-button label="answer">我的作答</el-radio-button>
|
|
|
+ <el-radio-button label="analysis">答案解析</el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ </div>
|
|
|
+ <div class="card detail_card answer_card">
|
|
|
+ <div class="score_tags">
|
|
|
+ <span class="score_tag">得分:{{ selectedQuestion.score }}</span>
|
|
|
+ <span class="score_tag">得分率:{{ selectedQuestion.rate }}%</span>
|
|
|
+ <span v-if="selectedQuestion.modelText" class="model_tag" :class="`model_${selectedQuestion.model}`">
|
|
|
+ {{ selectedQuestion.modelText }}
|
|
|
</span>
|
|
|
</div>
|
|
|
- <div class="score_row">
|
|
|
- <span>得分:{{ selectedQuestion.score }}</span>
|
|
|
- <span>得分率:{{ selectedQuestion.rate }}%</span>
|
|
|
+
|
|
|
+ <!-- 我的作答:writeLine 留痕图片 -->
|
|
|
+ <div v-if="detailTab === 'answer'" class="detail_content answer_content">
|
|
|
+ <template v-if="selectedQuestion.answerImages.length">
|
|
|
+ <el-image
|
|
|
+ v-for="(url, idx) in selectedQuestion.answerImages"
|
|
|
+ :key="idx"
|
|
|
+ class="answer_img"
|
|
|
+ :src="url"
|
|
|
+ fit="contain"
|
|
|
+ :preview-src-list="selectedQuestion.answerImages"
|
|
|
+ :z-index="3000">
|
|
|
+ </el-image>
|
|
|
+ </template>
|
|
|
+ <div v-else class="content_empty no_content_data">
|
|
|
+ <span>暂无作答内容</span>
|
|
|
+ </div>
|
|
|
</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 v-else class="detail_content analysis_content">
|
|
|
+ <template v-if="selectedQuestionHasAnalysis">
|
|
|
+ <div v-if="hasHtmlContent(selectedQuestion.answer)" class="answer_line">
|
|
|
+ <span class="line_label">【答案】</span>
|
|
|
+ <span class="line_value answer_value" v-html="selectedQuestion.answer"></span>
|
|
|
</div>
|
|
|
+ <div v-if="hasHtmlContent(selectedQuestion.parse)" class="parse_block" v-html="selectedQuestion.parse"></div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ class="sub_analysis"
|
|
|
+ v-for="(sub, sIdx) in selectedQuestion.subQuestions"
|
|
|
+ :key="sub.id || sIdx">
|
|
|
+ <div class="sub_analysis_title" v-if="sub.title" v-html="sub.title"></div>
|
|
|
+ <div v-if="hasHtmlContent(sub.answer)" class="answer_line">
|
|
|
+ <span class="line_label">【答案】</span>
|
|
|
+ <span class="line_value answer_value" v-html="sub.answer"></span>
|
|
|
+ </div>
|
|
|
+ <div v-if="hasHtmlContent(sub.parse)" class="parse_block" v-html="sub.parse"></div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div v-else class="content_empty no_content_data">
|
|
|
+ <span>暂无解析数据</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <div v-else class="analysis_panel">{{ selectedQuestion.analysis }}</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </section>
|
|
|
+ </div>
|
|
|
|
|
|
<!-- 小题分析表 -->
|
|
|
<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 class="el_button">
|
|
|
+ <el-button class="export_btn" @click="exportQuestionTable">导出Excel</el-button>
|
|
|
+ </div>
|
|
|
</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 class="page_table analysis_table_wrap">
|
|
|
+ <el-table
|
|
|
+ :data="questionPage"
|
|
|
+ height="572"
|
|
|
+ stripe
|
|
|
+ class="el-table--border analysis_table">
|
|
|
+ <el-table-column prop="name" label="小题名称" min-width="120" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="typeName" label="题型" min-width="90" />
|
|
|
+ <el-table-column label="知识点" min-width="180" show-overflow-tooltip>
|
|
|
+ <template slot-scope="scope">{{ scope.row.knowledgeTags.join(',') }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="得分" min-width="100">
|
|
|
+ <template slot-scope="scope">{{ scope.row.score }} / {{ scope.row.scoreFull }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="得分率" min-width="100">
|
|
|
+ <template slot-scope="scope">{{ scope.row.rate }}%</template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ <div class="page_pagination total" v-if="questionRows.length">
|
|
|
+ <span class="page_total">共{{ questionRows.length }}条数据</span>
|
|
|
+ <el-pagination
|
|
|
+ background
|
|
|
+ layout="prev, pager, next"
|
|
|
+ :current-page.sync="questionPageNum"
|
|
|
+ :page-size="questionPageSize"
|
|
|
+ :total="Number(questionRows.length)" />
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</section>
|
|
|
|
|
|
@@ -166,47 +256,63 @@
|
|
|
</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" />
|
|
|
+ <template v-if="knowledgeRows.length">
|
|
|
+ <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" />
|
|
|
+ </template>
|
|
|
+ <div v-else-if="!loading" class="empty_tip no_content_data">
|
|
|
+ <span>暂无数据</span>
|
|
|
+ </div>
|
|
|
</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 class="el_button">
|
|
|
+ <el-button class="export_btn" @click="exportKnowledgeTable">导出Excel</el-button>
|
|
|
+ </div>
|
|
|
</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 class="page_table analysis_table_wrap">
|
|
|
+ <el-table
|
|
|
+ :data="knowledgePage"
|
|
|
+ height="572"
|
|
|
+ stripe
|
|
|
+ class="el-table--border analysis_table">
|
|
|
+ <el-table-column prop="name" label="知识点" min-width="200" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="questions" label="包含小题" min-width="180" show-overflow-tooltip />
|
|
|
+ <el-table-column label="得分" min-width="100">
|
|
|
+ <template slot-scope="scope">{{ scope.row.score }} / {{ scope.row.fullScore }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="得分率" min-width="100">
|
|
|
+ <template slot-scope="scope">{{ scope.row.rate }}%</template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ <div class="page_pagination total" v-if="knowledgeRows.length">
|
|
|
+ <span class="page_total">共{{ knowledgeRows.length }}条数据</span>
|
|
|
+ <el-pagination
|
|
|
+ background
|
|
|
+ layout="prev, pager, next"
|
|
|
+ :current-page.sync="knowledgePageNum"
|
|
|
+ :page-size="knowledgePageSize"
|
|
|
+ :total="Number(knowledgeRows.length)" />
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</section>
|
|
|
|
|
|
- <!-- 学情总结 -->
|
|
|
- <section class="card" ref="summary">
|
|
|
+ <!-- 学情总结(接口暂无该数据时隐藏) -->
|
|
|
+ <section class="card" ref="summary" v-if="hasSummary">
|
|
|
<div class="card_title">学情总结</div>
|
|
|
<div class="summary_block">
|
|
|
<span class="summary_label strength">优势</span>
|
|
|
@@ -234,55 +340,122 @@ 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。',
|
|
|
+// 接口数值字段可能返回字符串,统一转数字
|
|
|
+function toNumber(val, fallback = 0) {
|
|
|
+ const n = Number(val);
|
|
|
+ return Number.isFinite(n) ? n : fallback;
|
|
|
+}
|
|
|
+
|
|
|
+const OPTION_KEYS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
|
+
|
|
|
+// 题型:1-单选题 2-多选题 3-对错题(判断题)
|
|
|
+const QUESTION_TYPE_MAP = { 1: '单选题', 2: '多选题', 3: '判断题' };
|
|
|
+
|
|
|
+const ANSWER_MODEL_MAP = { 0: '', 1: '优秀作答', 2: '典型错误' };
|
|
|
+
|
|
|
+// 选项列表统一为 [{ key, text }],columnName 为 A/B/C 字段值,按 sort 排序
|
|
|
+function mapOptionList(list) {
|
|
|
+ if (!Array.isArray(list)) return [];
|
|
|
+ return [...list]
|
|
|
+ .sort((a, b) => toNumber(a.sort, 0) - toNumber(b.sort, 0))
|
|
|
+ .map((opt, idx) => {
|
|
|
+ if (opt == null) return { key: OPTION_KEYS[idx] || String(idx + 1), text: '' };
|
|
|
+ if (typeof opt === 'string') return { key: OPTION_KEYS[idx] || String(idx + 1), text: opt };
|
|
|
+ return {
|
|
|
+ key: opt.columnName || OPTION_KEYS[idx] || String(idx + 1),
|
|
|
+ text: opt.content || '',
|
|
|
+ };
|
|
|
});
|
|
|
+}
|
|
|
+
|
|
|
+// 知识点列表统一为字符串数组
|
|
|
+function mapKnowledgeList(list, fallback) {
|
|
|
+ if (Array.isArray(list) && list.length) {
|
|
|
+ const names = list.map(item => (item && item.knowName) || '').filter(Boolean);
|
|
|
+ if (names.length) return names;
|
|
|
}
|
|
|
- return rows;
|
|
|
+ return fallback ? [String(fallback)] : [];
|
|
|
}
|
|
|
|
|
|
-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,
|
|
|
- });
|
|
|
+// writeLine 为作答留痕数据(JSON),兼容字符串/数组/对象多种形态,提取作答图片地址
|
|
|
+function parseWriteLine(raw) {
|
|
|
+ if (!raw) return [];
|
|
|
+ let data = raw;
|
|
|
+ if (typeof raw === 'string') {
|
|
|
+ const text = raw.trim();
|
|
|
+ if (!text) return [];
|
|
|
+ try {
|
|
|
+ data = JSON.parse(text);
|
|
|
+ } catch (e) {
|
|
|
+ // 非 JSON 的纯字符串视为单个图片地址
|
|
|
+ return /^https?:\/\//.test(text) ? [text] : [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const pick = obj => {
|
|
|
+ if (!obj || typeof obj !== 'object') return [];
|
|
|
+ const urls = obj.writeUrls || obj.urls || obj.imgUrls || obj.images || obj.imgs;
|
|
|
+ if (Array.isArray(urls)) return urls;
|
|
|
+ return obj.url || obj.imageUrl ? [obj.url || obj.imageUrl] : [];
|
|
|
+ };
|
|
|
+ if (Array.isArray(data)) {
|
|
|
+ return data.reduce((acc, item) => {
|
|
|
+ if (typeof item === 'string') {
|
|
|
+ if (/^https?:\/\//.test(item)) acc.push(item);
|
|
|
+ } else {
|
|
|
+ pick(item).forEach(url => acc.push(url));
|
|
|
+ }
|
|
|
+ return acc;
|
|
|
+ }, []).filter(Boolean);
|
|
|
}
|
|
|
- return rows;
|
|
|
+ return pick(data).filter(Boolean);
|
|
|
+}
|
|
|
+
|
|
|
+// 子题(subQuestions)映射
|
|
|
+function mapSubQuestion(item, index) {
|
|
|
+ return {
|
|
|
+ id: item.questionId || index,
|
|
|
+ title: item.title || '',
|
|
|
+ answer: item.answer || '',
|
|
|
+ parse: item.parse || '',
|
|
|
+ options: mapOptionList(item.optionList),
|
|
|
+ typeName: item.questionTypeName || QUESTION_TYPE_MAP[item.questionTypeId] || '',
|
|
|
+ isJudge: item.isJudge,
|
|
|
+ knowledgeTags: mapKnowledgeList(item.knowledgeList),
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+// 学生题目列表(studentQuestionVOList)映射为小题行
|
|
|
+function mapQuestionRow(item, index) {
|
|
|
+ return {
|
|
|
+ id: item.questionId || item.id || index,
|
|
|
+ name: item.questionName || '-',
|
|
|
+ type: toNumber(item.questionType),
|
|
|
+ typeName: QUESTION_TYPE_MAP[item.questionType] || '',
|
|
|
+ knowledgeTags: mapKnowledgeList(item.knowledgeList, item.knowledge),
|
|
|
+ title: item.title || '',
|
|
|
+ answer: item.answer || '',
|
|
|
+ parse: item.parse || '',
|
|
|
+ options: mapOptionList(item.optionList),
|
|
|
+ answerImages: parseWriteLine(item.writeLine),
|
|
|
+ modelText: ANSWER_MODEL_MAP[item.model] || '',
|
|
|
+ model: toNumber(item.model),
|
|
|
+ score: item.score === '' || item.score == null ? '-' : item.score,
|
|
|
+ scoreFull: item.scoreFull === '' || item.scoreFull == null ? '-' : item.scoreFull,
|
|
|
+ rate: toNumber(item.scoreRate),
|
|
|
+ subQuestions: Array.isArray(item.subQuestions) ? item.subQuestions.map(mapSubQuestion) : [],
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+// 学生知识点列表(studentKnowVOList)映射为知识点行,scoreRate 已是百分数值只需加 %
|
|
|
+function mapKnowledgeRow(item, index) {
|
|
|
+ return {
|
|
|
+ id: index,
|
|
|
+ name: item.knowledge || item.questionName || '-',
|
|
|
+ questions: item.questionName || '',
|
|
|
+ score: item.score == null ? '-' : item.score,
|
|
|
+ fullScore: item.scoreFull == null ? '-' : item.scoreFull,
|
|
|
+ rate: toNumber(item.scoreRate),
|
|
|
+ };
|
|
|
}
|
|
|
|
|
|
export default {
|
|
|
@@ -294,10 +467,13 @@ export default {
|
|
|
data() {
|
|
|
return {
|
|
|
homework: null,
|
|
|
+ loading: false,
|
|
|
activeNav: 'overview',
|
|
|
scrollLock: false,
|
|
|
scrollTimer: null,
|
|
|
- navList: [
|
|
|
+ // 导出中,防止重复点击
|
|
|
+ exporting: false,
|
|
|
+ allNavList: [
|
|
|
{ key: 'overview', label: '作业概况' },
|
|
|
{ key: 'qChart', label: '小题分析图' },
|
|
|
{ key: 'qTable', label: '小题分析表' },
|
|
|
@@ -306,34 +482,29 @@ export default {
|
|
|
{ key: 'summary', label: '学情总结' },
|
|
|
],
|
|
|
overview: {
|
|
|
- fullScore: 100,
|
|
|
- score: 65,
|
|
|
- rate: '63.26%',
|
|
|
- correctCount: 9,
|
|
|
- questionCount: 65,
|
|
|
+ fullScore: 0,
|
|
|
+ score: 0,
|
|
|
+ rate: 0,
|
|
|
+ correctCount: 0,
|
|
|
+ questionCount: 0,
|
|
|
},
|
|
|
- questionRows: buildQuestionRows(),
|
|
|
- knowledgeRows: buildKnowledgeRows(),
|
|
|
+ questionRows: [],
|
|
|
+ knowledgeRows: [],
|
|
|
qChartType: 'vertical_bar',
|
|
|
kChartType: 'vertical_bar',
|
|
|
chartTypeList: [
|
|
|
{ label: '柱状图', value: 'vertical_bar' },
|
|
|
{ label: '雷达图', value: 'radar_chart' },
|
|
|
],
|
|
|
- selectedIndex: 1,
|
|
|
+ selectedIndex: 0,
|
|
|
detailTab: 'answer',
|
|
|
questionPageNum: 1,
|
|
|
knowledgePageNum: 1,
|
|
|
- pageSize: 5,
|
|
|
- summary: {
|
|
|
- strength: '本次作业总体得分稳定,基础题掌握较好,阅读理解类题目得分率较高。',
|
|
|
- weakness: '第2题及部分文言、表达类小题得分偏低,知识点迁移还不够熟练。',
|
|
|
- advice: [
|
|
|
- '针对失分小题回看题干和解析,整理同类题的答题步骤。',
|
|
|
- '优先复习得分率较低的知识点,并完成对应小题的订正。',
|
|
|
- '解答题注意分点作答,先写结论再补充依据。',
|
|
|
- ],
|
|
|
- },
|
|
|
+ // 两个表格前端分页,固定每页 10 条
|
|
|
+ questionPageSize: 10,
|
|
|
+ knowledgePageSize: 10,
|
|
|
+ // 接口未返回学情总结,无数据时隐藏该区块
|
|
|
+ summary: null,
|
|
|
};
|
|
|
},
|
|
|
computed: {
|
|
|
@@ -348,21 +519,36 @@ export default {
|
|
|
const name = (this.homework && this.homework.subjectName) || '';
|
|
|
return this.$global.getSubjectsName(name, null, 'color');
|
|
|
},
|
|
|
+ hasSummary() {
|
|
|
+ return !!(this.summary && (this.summary.strength || this.summary.weakness
|
|
|
+ || (Array.isArray(this.summary.advice) && this.summary.advice.length)));
|
|
|
+ },
|
|
|
+ navList() {
|
|
|
+ return this.allNavList.filter(item => item.key !== 'summary' || this.hasSummary);
|
|
|
+ },
|
|
|
questionCount() {
|
|
|
- if (this.homework && this.homework.questionNum != null) return this.homework.questionNum;
|
|
|
- return this.overview.questionCount;
|
|
|
+ if (this.overview.questionCount) return this.overview.questionCount;
|
|
|
+ return this.homework && this.homework.questionNum != null ? this.homework.questionNum : 0;
|
|
|
},
|
|
|
overviewList() {
|
|
|
return [
|
|
|
{ key: 'full', label: '满分', value: this.overview.fullScore, icon: require('@/assets/homeworkTask/fullMarks.png') },
|
|
|
{ key: 'score', label: '得分', value: this.overview.score, icon: require('@/assets/homeworkTask/score.png') },
|
|
|
- { key: 'rate', label: '得分率', value: this.overview.rate, icon: require('@/assets/homeworkTask/scoringRate.png') },
|
|
|
+ { key: 'rate', label: '得分率', value: `${this.overview.rate}%`, icon: require('@/assets/homeworkTask/scoringRate.png') },
|
|
|
{ key: 'correct', label: '答对题数', value: this.overview.correctCount, icon: require('@/assets/homeworkTask/correct.png') },
|
|
|
- { key: 'count', label: '试题数量', value: this.overview.questionCount, icon: require('@/assets/homeworkTask/quantity.png') },
|
|
|
+ { key: 'count', label: '试题数量', value: this.questionCount, icon: require('@/assets/homeworkTask/quantity.png') },
|
|
|
];
|
|
|
},
|
|
|
+ // 小题分析图展示全部小题
|
|
|
chartQuestions() {
|
|
|
- return this.questionRows.slice(0, 10);
|
|
|
+ return this.questionRows;
|
|
|
+ },
|
|
|
+ // 答案解析 tab 是否有内容(答案/解析,或子题的答案/解析)
|
|
|
+ selectedQuestionHasAnalysis() {
|
|
|
+ const q = this.selectedQuestion;
|
|
|
+ if (!q) return false;
|
|
|
+ if (this.hasHtmlContent(q.answer) || this.hasHtmlContent(q.parse)) return true;
|
|
|
+ return q.subQuestions.some(sub => this.hasHtmlContent(sub.answer) || this.hasHtmlContent(sub.parse));
|
|
|
},
|
|
|
chartKnowledge() {
|
|
|
return this.knowledgeRows.slice(0, 8);
|
|
|
@@ -371,12 +557,12 @@ export default {
|
|
|
return this.chartQuestions[this.selectedIndex] || null;
|
|
|
},
|
|
|
questionPage() {
|
|
|
- const start = (this.questionPageNum - 1) * this.pageSize;
|
|
|
- return this.questionRows.slice(start, start + this.pageSize);
|
|
|
+ const start = (this.questionPageNum - 1) * this.questionPageSize;
|
|
|
+ return this.questionRows.slice(start, start + this.questionPageSize);
|
|
|
},
|
|
|
knowledgePage() {
|
|
|
- const start = (this.knowledgePageNum - 1) * this.pageSize;
|
|
|
- return this.knowledgeRows.slice(start, start + this.pageSize);
|
|
|
+ const start = (this.knowledgePageNum - 1) * this.knowledgePageSize;
|
|
|
+ return this.knowledgeRows.slice(start, start + this.knowledgePageSize);
|
|
|
},
|
|
|
questionDatax() {
|
|
|
return this.chartQuestions.map(item => item.name);
|
|
|
@@ -416,6 +602,66 @@ export default {
|
|
|
return;
|
|
|
}
|
|
|
this.homework = cache.homework;
|
|
|
+ this.fetchAnalysis();
|
|
|
+ },
|
|
|
+ fetchAnalysis() {
|
|
|
+ const homework = this.homework || {};
|
|
|
+ const aidPaperClassId = homework.aidPaperClassId || homework.homeWorkId;
|
|
|
+ const userInfo = (this.$store.state.user && this.$store.state.user.userInfo) || {};
|
|
|
+ // 学生账号即学籍号
|
|
|
+ const registrationCode = userInfo.registrationCode || userInfo.loginName || '';
|
|
|
+ if (!aidPaperClassId || !registrationCode) {
|
|
|
+ this.resetAnalysis();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.loading = true;
|
|
|
+ const params = { aidPaperClassId, registrationCode };
|
|
|
+ this.$api.teachingHomework.getStudentAnalysis(params).then(res => {
|
|
|
+ this.applyAnalysis(res);
|
|
|
+ }).catch(err => {
|
|
|
+ const body = this.pickBody(err);
|
|
|
+ if (body) {
|
|
|
+ this.applyAnalysis(body);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.resetAnalysis();
|
|
|
+ }).finally(() => {
|
|
|
+ this.loading = false;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ // 公共请求把 code 为 0 的成功响应放进了 reject,这里两种都认
|
|
|
+ pickBody(payload) {
|
|
|
+ if (!payload) return null;
|
|
|
+ if (payload.code === 0 || payload.code === 200) return payload;
|
|
|
+ if (payload.data && (payload.data.code === 0 || payload.data.code === 200)) return payload.data;
|
|
|
+ if (payload.response) return this.pickBody(payload.response);
|
|
|
+ return null;
|
|
|
+ },
|
|
|
+ resetAnalysis() {
|
|
|
+ this.overview = { fullScore: 0, score: 0, rate: 0, correctCount: 0, questionCount: 0 };
|
|
|
+ this.questionRows = [];
|
|
|
+ this.knowledgeRows = [];
|
|
|
+ this.selectedIndex = 0;
|
|
|
+ this.questionPageNum = 1;
|
|
|
+ this.knowledgePageNum = 1;
|
|
|
+ },
|
|
|
+ applyAnalysis(res) {
|
|
|
+ const data = (res && res.data) || {};
|
|
|
+ this.overview = {
|
|
|
+ fullScore: toNumber(data.fullScore),
|
|
|
+ score: toNumber(data.studentScore),
|
|
|
+ rate: toNumber(data.rateScore),
|
|
|
+ correctCount: toNumber(data.rightNum),
|
|
|
+ questionCount: toNumber(data.questionNum),
|
|
|
+ };
|
|
|
+ // 新结构:studentQuestionVOList 直接为题目数组,不再分组
|
|
|
+ const questionList = Array.isArray(data.studentQuestionVOList) ? data.studentQuestionVOList : [];
|
|
|
+ this.questionRows = questionList.map(mapQuestionRow);
|
|
|
+ const knowList = Array.isArray(data.studentKnowVOList) ? data.studentKnowVOList : [];
|
|
|
+ this.knowledgeRows = knowList.map(mapKnowledgeRow);
|
|
|
+ this.selectedIndex = 0;
|
|
|
+ this.questionPageNum = 1;
|
|
|
+ this.knowledgePageNum = 1;
|
|
|
},
|
|
|
scrollTo(key) {
|
|
|
const el = this.$refs[key];
|
|
|
@@ -423,7 +669,12 @@ export default {
|
|
|
if (!el || !box) return;
|
|
|
this.activeNav = key;
|
|
|
this.scrollLock = true;
|
|
|
- const top = el.getBoundingClientRect().top - box.getBoundingClientRect().top + box.scrollTop;
|
|
|
+ // 目标位置不能超过容器最大滚动距离(最后一个区块过高时顶部无法对齐容器顶部)
|
|
|
+ const max = box.scrollHeight - box.clientHeight;
|
|
|
+ const top = Math.min(
|
|
|
+ el.getBoundingClientRect().top - box.getBoundingClientRect().top + box.scrollTop,
|
|
|
+ max
|
|
|
+ );
|
|
|
box.scrollTo({ top, behavior: 'smooth' });
|
|
|
clearTimeout(this.scrollTimer);
|
|
|
const start = Date.now();
|
|
|
@@ -441,12 +692,18 @@ export default {
|
|
|
onScroll() {
|
|
|
if (this.scrollLock) return;
|
|
|
const box = this.$refs.mainScroll;
|
|
|
- if (!box) return;
|
|
|
- const scrollTop = box.scrollTop + 12;
|
|
|
+ if (!box || !this.navList.length) return;
|
|
|
+ // 已滚动到底部时,高亮最后一个导航项(末位区块过高、顶部无法到达容器顶部的场景)
|
|
|
+ if (box.scrollTop + box.clientHeight >= box.scrollHeight - 2) {
|
|
|
+ this.activeNav = this.navList[this.navList.length - 1].key;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const boxTop = box.getBoundingClientRect().top;
|
|
|
let current = this.navList[0].key;
|
|
|
this.navList.forEach(item => {
|
|
|
const el = this.$refs[item.key];
|
|
|
- if (el && el.offsetTop <= scrollTop) current = item.key;
|
|
|
+ // 用相对滚动容器的视口距离判定,避免 offsetTop 受 offsetParent 影响
|
|
|
+ if (el && el.getBoundingClientRect().top - boxTop <= 12) current = item.key;
|
|
|
});
|
|
|
this.activeNav = current;
|
|
|
},
|
|
|
@@ -455,43 +712,90 @@ export default {
|
|
|
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);
|
|
|
+ // 判断富文本是否有实际内容:纯 <p><br></p> 等空标签视为空,含图片或文字视为有内容
|
|
|
+ hasHtmlContent(content) {
|
|
|
+ if (!content) return false;
|
|
|
+ if (/<img[\s>]/i.test(content)) return true;
|
|
|
+ const text = String(content).replace(/<[^>]*>/g, '').replace(/ /g, '').trim();
|
|
|
+ return text.length > 0;
|
|
|
+ },
|
|
|
+ // 单元格空值统一显示 '-'
|
|
|
+ excelCell(value) {
|
|
|
+ return value === null || value === undefined || value === '' ? '-' : value;
|
|
|
+ },
|
|
|
+ // 调用通用导出接口并触发浏览器下载
|
|
|
+ downloadAnalysisExcel(payload) {
|
|
|
+ if (this.exporting) return;
|
|
|
+ this.exporting = true;
|
|
|
+ this.$api.teachingHomework.exportStudentAnalysis(payload).then(blob => {
|
|
|
+ // 后端业务异常时可能返回 JSON(仍被包装成 blob),读取后提示
|
|
|
+ if (blob && blob.type && blob.type.indexOf('application/json') !== -1) {
|
|
|
+ const reader = new FileReader();
|
|
|
+ reader.onload = () => {
|
|
|
+ try {
|
|
|
+ const msg = JSON.parse(reader.result).message || '导出失败';
|
|
|
+ this.$message.error(msg);
|
|
|
+ } catch (e) {
|
|
|
+ this.$message.error('导出失败');
|
|
|
+ }
|
|
|
+ };
|
|
|
+ reader.readAsText(blob);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const excelBlob = new Blob([blob], {
|
|
|
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
|
+ });
|
|
|
+ const link = document.createElement('a');
|
|
|
+ link.href = URL.createObjectURL(excelBlob);
|
|
|
+ link.download = payload.fileName;
|
|
|
+ link.click();
|
|
|
+ URL.revokeObjectURL(link.href);
|
|
|
+ }).catch(() => {
|
|
|
+ this.$message.error('导出失败,请稍后重试');
|
|
|
+ }).finally(() => {
|
|
|
+ this.exporting = false;
|
|
|
+ });
|
|
|
},
|
|
|
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}%` })));
|
|
|
+ const payload = {
|
|
|
+ fileName: '小题分析表.xlsx',
|
|
|
+ sheetName: '小题分析表',
|
|
|
+ headDataList: [['小题名称', '题型', '知识点', '得分', '得分率']],
|
|
|
+ dataList: this.questionRows.map(item => [
|
|
|
+ this.excelCell(item.name),
|
|
|
+ this.excelCell(item.typeName),
|
|
|
+ this.excelCell(item.knowledgeTags.join(',')),
|
|
|
+ `${this.excelCell(item.score)} / ${this.excelCell(item.scoreFull)}`,
|
|
|
+ `${this.excelCell(item.rate)}%`,
|
|
|
+ ]),
|
|
|
+ };
|
|
|
+ this.downloadAnalysisExcel(payload);
|
|
|
},
|
|
|
exportKnowledgeTable() {
|
|
|
- this.downloadExcel('知识点分析表.xls', [
|
|
|
- { label: '知识点', prop: 'name' },
|
|
|
- { label: '包含小题', prop: 'questions' },
|
|
|
- { label: '得分', prop: 'score' },
|
|
|
- { label: '得分率', prop: 'rateText' },
|
|
|
- ], this.knowledgeRows.map(item => ({ ...item, rateText: `${item.rate}%` })));
|
|
|
+ const payload = {
|
|
|
+ fileName: '知识点分析表.xlsx',
|
|
|
+ sheetName: '知识点分析表',
|
|
|
+ headDataList: [['知识点', '包含小题', '得分', '得分率']],
|
|
|
+ dataList: this.knowledgeRows.map(item => [
|
|
|
+ this.excelCell(item.name),
|
|
|
+ this.excelCell(item.questions),
|
|
|
+ `${this.excelCell(item.score)} / ${this.excelCell(item.fullScore)}`,
|
|
|
+ `${this.excelCell(item.rate)}%`,
|
|
|
+ ]),
|
|
|
+ };
|
|
|
+ this.downloadAnalysisExcel(payload);
|
|
|
},
|
|
|
handleAiExplain() {
|
|
|
this.$message.info('AI讲解待对接');
|
|
|
},
|
|
|
+ // 知识点标签超过 5 个字截断并补省略号,完整内容通过 title 悬浮展示
|
|
|
+ formatKnowledgeTag(tag) {
|
|
|
+ if (!tag) return '';
|
|
|
+ return tag.length > 5 ? `${tag.slice(0, 5)}…` : tag;
|
|
|
+ },
|
|
|
goBack() {
|
|
|
- this.$router.push('/teachingHomework/list');
|
|
|
+ // 从查看分析返回时,列表定位到「已提交」页签
|
|
|
+ this.$router.push({ path: '/teachingHomework/list', query: { status: 1 } });
|
|
|
},
|
|
|
},
|
|
|
};
|
|
|
@@ -631,6 +935,13 @@ export default {
|
|
|
justify-content: space-between;
|
|
|
margin-bottom: 12px;
|
|
|
}
|
|
|
+
|
|
|
+ .empty_tip {
|
|
|
+ min-height: 200px;
|
|
|
+ height: 200px;
|
|
|
+ width: 100%;
|
|
|
+ border-radius: 8px;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
.metric_row {
|
|
|
@@ -644,7 +955,7 @@ export default {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 10px;
|
|
|
- padding: 14px 16px;
|
|
|
+ padding: 16px 13px;
|
|
|
background: #fff;
|
|
|
border: 1px solid #EBEEF5;
|
|
|
border-radius: 8px;
|
|
|
@@ -676,190 +987,376 @@ export default {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- .question_detail {
|
|
|
+ /* 选中小题:题干 / 我的作答 两张独立卡片 */
|
|
|
+ .question_detail_wrap {
|
|
|
display: flex;
|
|
|
- gap: 16px;
|
|
|
- margin-top: 8px;
|
|
|
- padding-top: 16px;
|
|
|
- border-top: 1px solid #EBEEF5;
|
|
|
+ align-items: stretch;
|
|
|
+ gap: 10px;
|
|
|
+ margin-bottom: 10px;
|
|
|
+
|
|
|
+ .detail_card {
|
|
|
+ min-width: 0;
|
|
|
+ margin-bottom: 0;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 左题干卡固定宽度 */
|
|
|
+ .stem_card {
|
|
|
+ flex: 0 0 410px;
|
|
|
+ width: 410px;
|
|
|
+ padding: 20px;
|
|
|
+ }
|
|
|
|
|
|
- .detail_left,
|
|
|
- .detail_right {
|
|
|
+ /* 右列:tabs 卡 + 作答卡 上下排列,间隔 10px */
|
|
|
+ .answer_col {
|
|
|
flex: 1;
|
|
|
min-width: 0;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 10px;
|
|
|
+
|
|
|
+ /* 下方作答卡占满剩余高度 */
|
|
|
+ .answer_card {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ min-height: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* tabs 独立卡片,切换按钮复用全局 el-radio-button 公共用法(同 list.vue) */
|
|
|
+ .detail_tabs_card {
|
|
|
+ flex-shrink: 0;
|
|
|
+ margin-bottom: 0;
|
|
|
+ padding: 10px 20px;
|
|
|
+
|
|
|
+ .detail_radio {
|
|
|
+ :deep(.el-radio-button__inner) {
|
|
|
+ width: 76px;
|
|
|
+ height: 36px;
|
|
|
+ line-height: 36px;
|
|
|
+ padding: 0;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #606266;
|
|
|
+ background: #fff;
|
|
|
+ border-color: #DCDFE6;
|
|
|
+ box-shadow: none !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-radio-button:first-child .el-radio-button__inner) {
|
|
|
+ border-radius: 4px 0 0 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-radio-button:last-child .el-radio-button__inner) {
|
|
|
+ border-radius: 0 4px 4px 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-radio-button__orig-radio:checked + .el-radio-button__inner) {
|
|
|
+ color: #fff;
|
|
|
+ background-color: #2E64FA;
|
|
|
+ border-color: #2E64FA;
|
|
|
+ box-shadow: none !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
.detail_head {
|
|
|
+ flex-shrink: 0;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
- margin-bottom: 12px;
|
|
|
+ margin-bottom: 16px;
|
|
|
}
|
|
|
|
|
|
.detail_title {
|
|
|
- font-size: 15px;
|
|
|
+ font-size: 16px;
|
|
|
font-weight: 550;
|
|
|
color: #333;
|
|
|
+ line-height: 24px;
|
|
|
}
|
|
|
|
|
|
.ai_btn {
|
|
|
- height: 32px;
|
|
|
- padding: 0 12px;
|
|
|
+ height: 36px;
|
|
|
+ padding: 0 20px;
|
|
|
border: none;
|
|
|
- border-radius: 4px;
|
|
|
- background: #847BE6;
|
|
|
+ border-radius: 6px;
|
|
|
+ background: linear-gradient(270deg, #6659FF 0%, #9A57FF 60.38%, #F292FF 100%);
|
|
|
color: #fff;
|
|
|
+ font-size: 14px;
|
|
|
|
|
|
&:hover,
|
|
|
&:focus {
|
|
|
- background: #847BE6;
|
|
|
- color: #fff;
|
|
|
opacity: 0.92;
|
|
|
+ color: #fff;
|
|
|
+ background: linear-gradient(270deg, #6659FF 0%, #9A57FF 60.38%, #F292FF 100%);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.knowledge_tags {
|
|
|
+ flex-shrink: 0;
|
|
|
display: flex;
|
|
|
flex-wrap: wrap;
|
|
|
+ align-items: center;
|
|
|
gap: 8px;
|
|
|
- margin-bottom: 12px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+
|
|
|
+ .knowledge_label {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 550;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
|
|
|
.knowledge_tag {
|
|
|
display: inline-flex;
|
|
|
align-items: center;
|
|
|
- height: 28px;
|
|
|
- padding: 0 10px;
|
|
|
- border: 1px solid #409EFF;
|
|
|
+ height: 30px;
|
|
|
+ padding: 0 12px;
|
|
|
+ border: 1px solid #2E64FA;
|
|
|
border-radius: 4px;
|
|
|
- color: #409EFF;
|
|
|
+ color: #2E64FA;
|
|
|
font-size: 13px;
|
|
|
+ line-height: 1;
|
|
|
background: #fff;
|
|
|
box-sizing: border-box;
|
|
|
+ white-space: nowrap;
|
|
|
+
|
|
|
+ &.sm {
|
|
|
+ height: 26px;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .stem_scroll {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding-right: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stem_text {
|
|
|
+ font-weight: 550;
|
|
|
+ font-size: 16px;
|
|
|
+ line-height: 1.8;
|
|
|
+ color: #333333;
|
|
|
+
|
|
|
+ :deep(img) {
|
|
|
+ max-width: 100%;
|
|
|
+ vertical-align: middle;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- .stem_text,
|
|
|
.option_item {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ margin-top: 12px;
|
|
|
font-size: 14px;
|
|
|
line-height: 1.7;
|
|
|
color: #333;
|
|
|
+
|
|
|
+ .opt_key {
|
|
|
+ flex-shrink: 0;
|
|
|
+ margin-right: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .opt_text {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+
|
|
|
+ :deep(img) {
|
|
|
+ max-width: 100%;
|
|
|
+ vertical-align: middle;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- .option_item {
|
|
|
- margin-top: 6px;
|
|
|
+ .sub_question {
|
|
|
+ padding-top: 16px;
|
|
|
+
|
|
|
+ .sub_title {
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 1.8;
|
|
|
+ color: #333;
|
|
|
+ font-weight: 550;
|
|
|
+
|
|
|
+ :deep(img) {
|
|
|
+ max-width: 100%;
|
|
|
+ vertical-align: middle;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .knowledge_tags {
|
|
|
+ margin: 10px 0 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .option_item:first-child {
|
|
|
+ margin-top: 12px;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- .detail_tabs {
|
|
|
+ /* tabs 已抽为上方独立卡片,使用公共 el-radio-button 样式 */
|
|
|
+
|
|
|
+ .score_tags {
|
|
|
+ flex-shrink: 0;
|
|
|
display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 24px;
|
|
|
- margin-bottom: 12px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 12px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+
|
|
|
+ .score_tag {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ height: 38px;
|
|
|
+ padding: 0 16px;
|
|
|
+ background: #EAF1FF;
|
|
|
+ border-radius: 6px;
|
|
|
+ color: #2E64FA;
|
|
|
+ font-size: 14px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
|
|
|
- .detail_tab {
|
|
|
- position: relative;
|
|
|
- height: 32px;
|
|
|
- line-height: 32px;
|
|
|
+ .model_tag {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ height: 38px;
|
|
|
+ padding: 0 16px;
|
|
|
+ border-radius: 6px;
|
|
|
font-size: 14px;
|
|
|
- color: #909399;
|
|
|
- cursor: pointer;
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
|
- &.active {
|
|
|
- color: #409EFF;
|
|
|
- font-weight: 550;
|
|
|
+ &.model_1 {
|
|
|
+ background: #E8F7EE;
|
|
|
+ color: #67C23A;
|
|
|
+ }
|
|
|
|
|
|
- &::after {
|
|
|
- content: '';
|
|
|
- position: absolute;
|
|
|
- left: 0;
|
|
|
- right: 0;
|
|
|
- bottom: 0;
|
|
|
- height: 2px;
|
|
|
- background: #409EFF;
|
|
|
- }
|
|
|
+ &.model_2 {
|
|
|
+ background: #FEF0F0;
|
|
|
+ color: #F56C6C;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- .score_row {
|
|
|
- display: flex;
|
|
|
- gap: 16px;
|
|
|
- margin-bottom: 12px;
|
|
|
+ .detail_content {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 360px;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 16px;
|
|
|
+ background: #F5F7FA;
|
|
|
+ border-radius: 8px;
|
|
|
+ box-sizing: border-box;
|
|
|
font-size: 14px;
|
|
|
- color: #409EFF;
|
|
|
+ line-height: 1.7;
|
|
|
+ color: #666;
|
|
|
}
|
|
|
|
|
|
- .paper_box {
|
|
|
- min-height: 220px;
|
|
|
- padding: 16px;
|
|
|
- border: 1px solid #EBEEF5;
|
|
|
- border-radius: 4px;
|
|
|
- background: #FAFBFC;
|
|
|
- box-sizing: border-box;
|
|
|
+ .answer_content {
|
|
|
+ text-align: center;
|
|
|
+ display: flex;
|
|
|
|
|
|
- .paper_title {
|
|
|
- margin-bottom: 12px;
|
|
|
- font-size: 14px;
|
|
|
- color: #333;
|
|
|
+ .answer_img {
|
|
|
+ display: block;
|
|
|
+ width: 100%;
|
|
|
+ max-width: 560px;
|
|
|
+ margin: 0 auto;
|
|
|
+ border-radius: 4px;
|
|
|
+ background: #fff;
|
|
|
+
|
|
|
+ :deep(img) {
|
|
|
+ width: 100%;
|
|
|
+ height: auto;
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+
|
|
|
+ & + .answer_img {
|
|
|
+ margin-top: 12px;
|
|
|
+ }
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- .paper_line {
|
|
|
- height: 10px;
|
|
|
- margin-bottom: 10px;
|
|
|
- background: #EEF1F6;
|
|
|
- border-radius: 2px;
|
|
|
+ .analysis_content {
|
|
|
+ .answer_line {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 1.8;
|
|
|
|
|
|
- &:nth-child(odd) {
|
|
|
- width: 92%;
|
|
|
+ .line_label {
|
|
|
+ flex-shrink: 0;
|
|
|
+ color: #333;
|
|
|
+ font-weight: 550;
|
|
|
}
|
|
|
|
|
|
- &:nth-child(even) {
|
|
|
- width: 70%;
|
|
|
+ .answer_value {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ color: #2E64FA;
|
|
|
+ font-weight: 550;
|
|
|
+
|
|
|
+ :deep(p) {
|
|
|
+ margin: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(img) {
|
|
|
+ max-width: 100%;
|
|
|
+ vertical-align: middle;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- .paper_choice {
|
|
|
+ .parse_block {
|
|
|
margin-top: 8px;
|
|
|
font-size: 14px;
|
|
|
+ line-height: 1.8;
|
|
|
color: #666;
|
|
|
+
|
|
|
+ :deep(p) {
|
|
|
+ margin: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(img) {
|
|
|
+ max-width: 100%;
|
|
|
+ vertical-align: middle;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .sub_analysis {
|
|
|
+ margin-top: 18px;
|
|
|
+ padding-top: 14px;
|
|
|
+ border-top: 1px dashed #DCDFE6;
|
|
|
+
|
|
|
+ .sub_analysis_title {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 550;
|
|
|
+ color: #333;
|
|
|
+ line-height: 1.8;
|
|
|
+ margin-bottom: 6px;
|
|
|
+
|
|
|
+ :deep(img) {
|
|
|
+ max-width: 100%;
|
|
|
+ vertical-align: middle;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- .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;
|
|
|
+ .content_empty {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 200px;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- .export_btn {
|
|
|
- height: 32px;
|
|
|
- padding: 0 12px;
|
|
|
- color: #2E64FA;
|
|
|
- border-color: #2E64FA;
|
|
|
- background: #fff;
|
|
|
- }
|
|
|
+ /* 导出按钮使用全局 .el_button .export_btn 公共样式(导出图标 + hover 变蓝) */
|
|
|
|
|
|
- .analysis_table {
|
|
|
+ /* 表格使用全局 .page_table 公共样式,这里只控制宽度与分页间距 */
|
|
|
+ .analysis_table_wrap {
|
|
|
width: 100%;
|
|
|
|
|
|
- :deep(th) {
|
|
|
- background: #F5F7FA;
|
|
|
- color: #333;
|
|
|
- font-weight: 550;
|
|
|
+ .el-table {
|
|
|
+ width: 100%;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- .pager_row {
|
|
|
- display: flex;
|
|
|
- justify-content: flex-end;
|
|
|
- margin-top: 12px;
|
|
|
- }
|
|
|
+ /* 分页器使用全局 .page_pagination 公共样式 */
|
|
|
|
|
|
.summary_block {
|
|
|
display: flex;
|