Răsfoiți Sursa

新增教辅作业

吴朋磊 3 săptămâni în urmă
părinte
comite
55b034cf4a

BIN
src/assets/homeworkTask/camera_999.png


BIN
src/assets/homeworkTask/camera_fff.png


+ 3 - 1
src/http/api.js

@@ -3,10 +3,12 @@ import reportStudent from "./api/reportStudent";
 import personalProfile from "./api/personalProfile";
 
 import target from "./api/target";//靶向提升接口文档
+import teachingHomework from "./api/teachingHomework";//教辅作业
 
 export default {
     user,  
     reportStudent,
     personalProfile,
-    target
+    target,
+    teachingHomework
 }

+ 14 - 0
src/http/api/teachingHomework.js

@@ -0,0 +1,14 @@
+// 教辅作业相关接口
+import { get, post } from '../common/http';
+import base from '../common/base';
+
+const teachingHomework = {
+  // 作业任务列表
+  // params: { subjectCode, submitStatus, keyword, pageNum, pageSize }
+  // submitStatus: 0未提交 1已提交
+  getHomeworkList(data) {
+    return post(base.prefix + `/api/v1/studentHomework/list`, data);
+  },
+};
+
+export default teachingHomework;

+ 39 - 0
src/router/index.js

@@ -62,6 +62,45 @@ let constantRoutes = [
             },
         ],
     },
+    //教辅作业
+    {
+        path: "/teachingHomework",
+        redirect: "/teachingHomework/list",
+        component: () => import("@/views/teachingHomework/index"),
+        meta: {
+            title: "教辅作业",
+        },
+        children: [
+            {
+                path: "list",
+                component: () => import("@/views/teachingHomework/homeworkTask/list.vue"),
+                meta: {
+                    title: "教辅作业",
+                },
+            },
+            {
+                path: "answer",
+                component: () => import("@/views/teachingHomework/homeworkTask/answer.vue"),
+                meta: {
+                    title: "作业作答",
+                },
+            },
+            {
+                path: "viewAnswer",
+                component: () => import("@/views/teachingHomework/homeworkTask/viewAnswer.vue"),
+                meta: {
+                    title: "查看作答",
+                },
+            },
+            {
+                path: "analysis",
+                component: () => import("@/views/teachingHomework/homeworkTask/analysis.vue"),
+                meta: {
+                    title: "查看分析",
+                },
+            },
+        ],
+    },
     // 个人中心
     {
         path: "/userInfo",

+ 1 - 1
src/views/analysisReport/studentPage/mainPage.vue

@@ -40,7 +40,7 @@
                                     @click="toPage(pathFive)">报告打印</button>
                             </div>
                             <!-- 520929546006958081 -->
-                            <div class="right">
+                            <div class="right"> 
                                 <div class="page_filter" ref="filterContent"
                                     v-if="activeBtn != pathFourth && activeBtn != pathFive">
                                     <FiltersItem :filtersData="filterData" @selectItem="ChangeFilters">

+ 7 - 2
src/views/layout/components/Header.vue

@@ -200,7 +200,7 @@ export default {
 
     this.activeMenu();
 
-    //学生端默认 个人中心 和分析报告两个菜单
+    //学生端默认菜单
     const baseMenuList = [
       {
         menuName: "分析报告",
@@ -210,11 +210,14 @@ export default {
         menuName: "靶向提升",
         externalLink: "/targetImprove",
       },
+      {
+        menuName: "教辅作业",
+        externalLink: "/teachingHomework",
+      },
       {
         menuName: "个人中心",
         externalLink: "/userInfo",
       },
-     
     ];
   
     this.menuList = baseMenuList;
@@ -270,6 +273,8 @@ export default {
         this.activeMenuName = '/jointStudentAnalysisReport/list';
       }else if(path == '/targetImprove' || path.startsWith('/targetImprove/')){
         this.activeMenuName = '/targetImprove';
+      }else if(path == '/teachingHomework' || path.startsWith('/teachingHomework/')){
+        this.activeMenuName = '/teachingHomework';
       }
        else if (path === '/userInfo' || path.startsWith('/userInfo/')) {
         this.activeMenuName = '/userInfo';

+ 895 - 0
src/views/teachingHomework/homeworkTask/analysis.vue

@@ -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>

+ 822 - 0
src/views/teachingHomework/homeworkTask/answer.vue

@@ -0,0 +1,822 @@
+<template>
+  <!-- 教辅作业作答页。路由固定为 /teachingHomework/answer,不使用地址栏参数 -->
+  <div class="homework_answer" 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 class="meta_row">
+          <span>布置教师:{{ homework.teacherName }}</span>
+          <span>试题数量:{{ questions.length }}</span>
+          <span>结束时间:{{ homework.endTime || '-' }}</span>
+          <span class="countdown">倒计时:{{ countdownText }}</span>
+          <span class="progress">作答进度:{{ answeredCount }}/{{ questions.length }}</span>
+        </div>
+      </div>
+    </div>
+
+    <!-- 主体:左侧试题序号,右侧题干 + 底部固定作答区 -->
+    <div class="answer_body">
+      <!-- 试题列表:单选题 / 解答题分组,序号可切换当前题 -->
+      <div class="question_nav">
+        <div class="nav_title">试题列表</div>
+        <div class="nav_group" v-for="group in questionGroups" :key="group.type">
+          <div class="group_name">{{ group.name }}</div>
+          <div class="num_grid">
+            <button v-for="item in group.list" :key="item.id" type="button" class="num_btn" :class="numClass(item)"
+              @click="selectQuestion(item.index)">
+              {{ item.index + 1 }}
+            </button>
+          </div>
+        </div>
+      </div>
+
+      <!-- 右侧题目:上方滚动题干,下方作答区不随题干滚动 -->
+      <div class="question_panel" v-if="currentQuestion">
+        <div class="question_scroll">
+          <div class="q_tags">
+            <span class="q_no">{{ currentQuestion.index + 1 }}</span>
+            <span class="q_type">{{ currentQuestion.typeName }}</span>
+            <span class="q_level">{{ currentQuestion.difficulty }}</span>
+          </div>
+          <div class="q_stem">{{ currentQuestion.stem }}</div>
+          <div v-if="currentQuestion.type === 'single'" class="q_options">
+            <div class="q_option" v-for="opt in currentQuestion.options" :key="opt.key">
+              <span class="opt_key">{{ opt.key }}.</span>
+              <span>{{ opt.text }}</span>
+            </div>
+          </div>
+        </div>
+
+        <!-- 作答区固定在题目卡片底部。选择题选选项,解答题只拍照,没有手写输入框 -->
+        <div class="answer_dock">
+          <!-- 选择题:我的作答 + 选项;解答题:已上传图片,可删除 -->
+          <div class="dock_answer">
+            <span class="dock_label">我的作答:</span>
+            <span v-if="isSingle" class="dock_sub_no">(1)</span>
+            <el-radio-group v-if="isSingle" class="choice_radio" v-model="answers[currentQuestion.id]"
+              @change="persistState">
+              <el-radio v-for="opt in currentQuestion.options" :key="opt.key" :label="opt.key">
+                {{ opt.key }}
+              </el-radio>
+            </el-radio-group>
+            <div v-else class="photo_list">
+              <div
+                class="photo_item"
+                v-for="(img, imgIndex) in currentPhotos"
+                :key="imgIndex"
+                :class="{ active: imgIndex === 0 }">
+                <img :src="img" alt="" @click="previewPhoto(imgIndex)">
+                <i class="el-icon-close photo_remove" @click.stop="removePhoto(imgIndex)"></i>
+              </div>
+            </div>
+          </div>
+          <!-- 选择题禁用拍照;解答题可上传。最后一题把「下一题」换成「提交」 -->
+          <div class="dock_actions">
+            <el-button class="photo_btn" :type="isSingle ? 'default' : 'primary'" :disabled="isSingle"
+              @click="handlePhoto">
+              <img class="camera_icon" :src="isSingle ? cameraGray : cameraWhite" alt="">
+              拍照上传
+            </el-button>
+            <el-button class="nav_btn" :disabled="currentQuestion.index === 0" @click="prevQuestion">
+              上一题
+            </el-button>
+            <el-button v-if="!isLastQuestion" class="nav_btn" @click="nextQuestion">
+              下一题
+            </el-button>
+            <el-button v-else class="nav_btn" @click="submitAnswer">
+              提交
+            </el-button>
+          </div>
+          <input ref="photoInput" class="photo_input" type="file" accept="image/*" @change="onPhotoChange">
+        </div>
+      </div>
+    </div>
+
+    <!-- 图片放大预览:支持缩放、旋转,复用 Element UI ImageViewer -->
+    <el-image-viewer
+      v-if="imgVisible"
+      :z-index="9999"
+      :initial-index="previewIndex"
+      :url-list="currentPhotos"
+      :on-close="closePhotoPreview" />
+  </div>
+</template>
+
+<script>
+import ElImageViewer from 'element-ui/packages/image/src/image-viewer';
+
+// 作答状态只存 sessionStorage,刷新仍停留本页,且地址栏不带参数
+const STORAGE_KEY = 'teachingHomeworkAnswer';
+
+/**
+ * 临时试题数据,接口就绪后替换
+ * type: single 选择题(答案为选项字符串) / photo 解答题(答案为图片数组)
+ */
+function buildQuestions() {
+  const options = [
+    { key: 'A', text: '之:代词,指代前文所述内容' },
+    { key: 'B', text: '其:代词,他的' },
+    { key: 'C', text: '而:连词,表转折' },
+    { key: 'D', text: '以:介词,用、拿' },
+  ];
+  const list = [];
+  for (let i = 1; i <= 11; i += 1) {
+    list.push({
+      id: i,
+      type: 'single',
+      typeName: '选择题',
+      groupName: '单选题',
+      difficulty: '容易',
+      stem: `${i}、下列各句中,加点词的意义和用法相同的一组是`,
+      options,
+    });
+  }
+  for (let i = 12; i <= 16; i += 1) {
+    list.push({
+      id: i,
+      type: 'photo',
+      typeName: '解答题',
+      groupName: '解答题',
+      difficulty: '中等',
+      stem: `${i}、阅读材料,结合课文内容简要分析,并写出你的理解。`,
+    });
+  }
+  return list;
+}
+
+export default {
+  name: 'TeachingHomeworkAnswer',
+  components: {
+    ElImageViewer,
+  },
+  data() {
+    return {
+      homework: null, // 当前作业任务,来自 sessionStorage
+      questions: buildQuestions(), // 试题列表
+      currentIndex: 0, // 当前题下标
+      answers: {}, // 选择题为字符串,拍照题为图片 dataURL 数组
+      remainSeconds: 10 * 60, // 倒计时剩余秒数
+      timer: null,
+      imgVisible: false, // 是否打开图片放大预览
+      previewIndex: 0, // 预览起始图片下标
+      cameraGray: require('@/assets/homeworkTask/camera_999.png'),
+      cameraWhite: require('@/assets/homeworkTask/camera_fff.png'),
+    };
+  },
+  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');
+    },
+    // 按题型分组,供左侧序号导航使用
+    questionGroups() {
+      const groups = [];
+      this.questions.forEach((item, index) => {
+        let group = groups.find(g => g.type === item.type);
+        if (!group) {
+          group = { type: item.type, name: item.groupName, list: [] };
+          groups.push(group);
+        }
+        group.list.push({ ...item, index });
+      });
+      return groups;
+    },
+    // 当前正在作答的题目
+    currentQuestion() {
+      const item = this.questions[this.currentIndex];
+      if (!item) return null;
+      return { ...item, index: this.currentIndex };
+    },
+    // 当前题是否为选择题
+    isSingle() {
+      return !!(this.currentQuestion && this.currentQuestion.type === 'single');
+    },
+    // 是否最后一题,最后一题显示「提交」
+    isLastQuestion() {
+      return this.currentIndex >= this.questions.length - 1;
+    },
+    // 当前拍照题已上传的图片
+    currentPhotos() {
+      if (!this.currentQuestion || this.isSingle) return [];
+      const val = this.answers[this.currentQuestion.id];
+      return Array.isArray(val) ? val : [];
+    },
+    // 已作答题数,用于顶部进度
+    answeredCount() {
+      return this.questions.filter(item => this.isAnswered(item.id)).length;
+    },
+    // 倒计时 mm:ss
+    countdownText() {
+      const total = Math.max(0, this.remainSeconds);
+      const minute = String(Math.floor(total / 60)).padStart(2, '0');
+      const second = String(total % 60).padStart(2, '0');
+      return `${minute}:${second}`;
+    },
+  },
+  created() {
+    this.restoreState();
+  },
+  beforeDestroy() {
+    this.clearTimer();
+    this.persistState();
+  },
+  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;
+      this.currentIndex = Number(cache.currentIndex) || 0;
+      const savedAnswers = cache.answers && typeof cache.answers === 'object' ? cache.answers : {};
+      const answers = {};
+      this.questions.forEach(item => {
+        const saved = savedAnswers[item.id];
+        if (item.type === 'photo') {
+          answers[item.id] = Array.isArray(saved) ? saved : [];
+        } else {
+          answers[item.id] = saved != null && !Array.isArray(saved) ? saved : '';
+        }
+      });
+      this.answers = answers;
+      this.remainSeconds = Number(cache.remainSeconds);
+      if (!Number.isFinite(this.remainSeconds)) this.remainSeconds = 10 * 60;
+      if (this.currentIndex < 0 || this.currentIndex >= this.questions.length) {
+        this.currentIndex = 0;
+      }
+      this.startTimer();
+    },
+    // 保存作业、当前题、答案和剩余倒计时,保证刷新不丢失
+    persistState() {
+      if (!this.homework) return;
+      sessionStorage.setItem(STORAGE_KEY, JSON.stringify({
+        homework: this.homework,
+        currentIndex: this.currentIndex,
+        answers: this.answers,
+        remainSeconds: this.remainSeconds,
+      }));
+    },
+    // 倒计时每秒写入本地,刷新后从剩余时间继续
+    startTimer() {
+      this.clearTimer();
+      this.timer = setInterval(() => {
+        if (this.remainSeconds > 0) this.remainSeconds -= 1;
+        this.persistState();
+      }, 1000);
+    },
+    clearTimer() {
+      if (this.timer) {
+        clearInterval(this.timer);
+        this.timer = null;
+      }
+    },
+    // 选择题有选项、拍照题有图片,即视为已作答
+    isAnswered(id) {
+      const val = this.answers[id];
+      if (Array.isArray(val)) return val.length > 0;
+      return val !== undefined && val !== null && String(val).trim() !== '';
+    },
+    // 序号样式:当前题蓝底,已作答绿框,未作答蓝框
+    numClass(item) {
+      const answered = this.isAnswered(item.id);
+      return {
+        current: item.index === this.currentIndex,
+        done: answered && item.index !== this.currentIndex,
+      };
+    },
+    // 点击左侧序号切换题目
+    selectQuestion(index) {
+      this.currentIndex = index;
+      this.persistState();
+    },
+    // 上一题
+    prevQuestion() {
+      if (this.currentIndex <= 0) return;
+      this.currentIndex -= 1;
+      this.persistState();
+    },
+    // 下一题;最后一题不走这里,按钮会换成提交
+    nextQuestion() {
+      if (this.currentIndex >= this.questions.length - 1) return;
+      this.currentIndex += 1;
+      this.persistState();
+    },
+    // 选择题不可拍照;解答题打开本地选图
+    handlePhoto() {
+      if (this.isSingle) return;
+      const input = this.$refs.photoInput;
+      if (input) input.click();
+    },
+    // 选中图片后转成 dataURL,追加到当前题
+    onPhotoChange(e) {
+      const file = e.target.files && e.target.files[0];
+      e.target.value = '';
+      if (!file || !this.currentQuestion) return;
+      const reader = new FileReader();
+      reader.onload = () => {
+        const id = this.currentQuestion.id;
+        const list = Array.isArray(this.answers[id]) ? this.answers[id].slice() : [];
+        list.push(reader.result);
+        this.$set(this.answers, id, list);
+        this.persistState();
+      };
+      reader.readAsDataURL(file);
+    },
+    // 删除当前拍照题中的一张图片
+    removePhoto(index) {
+      if (!this.currentQuestion) return;
+      const id = this.currentQuestion.id;
+      const list = Array.isArray(this.answers[id]) ? this.answers[id].slice() : [];
+      list.splice(index, 1);
+      this.$set(this.answers, id, list);
+      this.persistState();
+    },
+    // 点击缩略图放大预览(可缩放、旋转)
+    previewPhoto(index) {
+      this.previewIndex = index;
+      this.imgVisible = true;
+    },
+    closePhotoPreview() {
+      this.imgVisible = false;
+    },
+    // 最后一题提交,接口未就绪
+    submitAnswer() {
+      this.persistState();
+      this.$message.success('提交待对接');
+    },
+    // 返回列表,作答进度仍保留在 sessionStorage
+    goBack() {
+      this.persistState();
+      this.$router.push('/teachingHomework/list');
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+/* 整页纵向:顶栏固定,下方主体占满剩余高度 */
+.homework_answer {
+  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;
+
+        .countdown {
+          color: #F56C6C;
+        }
+
+        .progress {
+          color: #2E64FA;
+        }
+      }
+    }
+  }
+
+  /* 左导航 + 右题目 */
+  .answer_body {
+    flex: 1;
+    min-height: 0;
+    display: flex;
+    gap: 10px;
+    margin-top: 10px;
+    overflow: hidden;
+
+    .question_nav {
+      flex-shrink: 0;
+      width: 240px;
+      height: 100%;
+      background: #fff;
+      border-radius: 10px;
+      box-sizing: border-box;
+      overflow-y: auto;
+
+      .nav_title {
+        font-size: 14px;
+        font-weight: 550;
+        color: #333;
+        padding: 10px;
+        border-bottom: 1px solid #EBEEF5;
+      }
+
+      .nav_group {
+        padding: 10px;
+
+        &+.nav_group {
+          margin-top: 24px;
+        }
+
+        .group_name {
+          font-size: 14px;
+          color: #333;
+          font-weight: 550;
+          padding-bottom: 12px;
+        }
+
+        .num_grid {
+          display: grid;
+          grid-template-columns: repeat(5, 32px);
+          gap: 15px;
+
+          /* 未作答蓝框,已作答绿框,当前题蓝底白字 */
+          .num_btn {
+            width: 32px;
+            height: 32px;
+            padding: 0;
+            border-radius: 4px;
+            border: 1px solid #2E64FA;
+            background: #fff;
+            color: #2E64FA;
+            font-size: 14px;
+            cursor: pointer;
+
+            &.done {
+              border-color: #3BA272;
+              color: #3BA272;
+            }
+
+            &.current {
+              background: #2E64FA;
+              border-color: #2E64FA;
+              color: #fff;
+            }
+          }
+        }
+      }
+    }
+
+    .question_panel {
+      flex: 1;
+      min-width: 0;
+      height: 100%;
+      display: flex;
+      flex-direction: column;
+      background: #fff;
+      border-radius: 10px;
+      overflow: hidden;
+
+      .question_scroll {
+        flex: 1;
+        min-height: 0;
+        overflow-y: auto;
+        padding: 16px;
+        box-sizing: border-box;
+
+        .q_tags {
+          display: flex;
+          align-items: center;
+          gap: 8px;
+          margin-bottom: 16px;
+
+          .q_no {
+            display: inline-flex;
+            align-items: center;
+            justify-content: center;
+            min-width: 24px;
+            height: 24px;
+            padding: 0 6px;
+            background: #F3F3F3;
+            border-radius: 2px;
+            font-weight: 550;
+            font-size: 14px;
+            color: #333333;
+            box-sizing: border-box;
+          }
+
+          .q_type {
+            font-weight: 550;
+            font-size: 14px;
+            color: #333333;
+          }
+
+          .q_level {
+            font-weight: 400;
+            font-size: 14px;
+            color: #90CB75;
+            margin-left: 8px;
+          }
+        }
+
+        .q_stem {
+          font-size: 15px;
+          line-height: 1.7;
+          color: #333;
+          margin-bottom: 16px;
+        }
+
+        .q_options {
+          .q_option {
+            display: flex;
+            gap: 6px;
+            margin-bottom: 10px;
+            font-size: 14px;
+            color: #333;
+            line-height: 1.6;
+
+            .opt_key {
+              flex-shrink: 0;
+            }
+          }
+        }
+      }
+
+      /* 作答区贴在题目卡片底部,不随题干滚动 */
+      .answer_dock {
+        flex-shrink: 0;
+        display: flex;
+        flex-direction: column;
+        align-items: stretch;
+        gap: 16px;
+        padding: 0 16px 16px 16px;
+        background: #fff;
+        box-sizing: border-box;
+
+        .dock_answer {
+          display: flex;
+          align-items: center;
+          gap: 16px;
+          min-height: 56px;
+          padding: 10px 16px;
+          background: #F5F7FA;
+          border-radius: 10px;
+          box-sizing: border-box;
+          border: 1px solid #DCDFE6;
+
+          .dock_label {
+            flex-shrink: 0;
+            display: inline-flex;
+            align-items: center;
+            height: 20px;
+            line-height: 20px;
+            font-size: 14px;
+            color: #333;
+          }
+
+          .dock_sub_no {
+            flex-shrink: 0;
+            display: inline-flex;
+            align-items: center;
+            height: 20px;
+            line-height: 20px;
+            font-size: 14px;
+            color: #333;
+          }
+
+          .choice_radio {
+            display: inline-flex;
+            align-items: center;
+            height: 20px;
+            gap: 20px;
+
+            :deep(.el-radio) {
+              margin-right: 0 !important;
+              height: 20px;
+              line-height: 20px;
+              display: inline-flex !important;
+              align-items: center;
+            }
+
+            :deep(.el-radio__input) {
+              display: inline-flex;
+              align-items: center;
+              height: 16px;
+              line-height: 16px;
+              vertical-align: middle;
+            }
+
+            :deep(.el-radio__inner) {
+              width: 16px;
+              height: 16px;
+              border: 1px solid #DCDFE6;
+              background: #fff;
+              vertical-align: middle;
+
+              &::after {
+                width: 10px;
+                height: 10px;
+                background-color: #2E64FA;
+                border-radius: 50%;
+                transform: translate(-50%, -50%) scale(0);
+              }
+            }
+
+            :deep(.el-radio__input.is-checked .el-radio__inner) {
+              border-color: #2E64FA;
+              background: #fff;
+
+              &::after {
+                transform: translate(-50%, -50%) scale(1);
+              }
+            }
+
+            :deep(.el-radio__input.is-checked + .el-radio__label) {
+              color: #333;
+            }
+
+            :deep(.el-radio__label) {
+              display: inline-flex;
+              align-items: center;
+              height: 20px;
+              padding-left: 8px;
+              font-size: 14px;
+              color: #333;
+              line-height: 20px;
+            }
+          }
+
+          .photo_list {
+            display: flex;
+            flex-wrap: wrap;
+            gap: 10px;
+            min-width: 0;
+
+            .photo_item {
+              position: relative;
+              width: 64px;
+              height: 64px;
+              border-radius: 4px;
+              border: 1px solid transparent;
+              box-sizing: border-box;
+              background: #fff;
+
+              &.active {
+                border-color: #2E64FA;
+              }
+
+              img {
+                width: 100%;
+                height: 100%;
+                object-fit: cover;
+                border-radius: 4px;
+                display: block;
+                cursor: zoom-in;
+              }
+
+              .photo_remove {
+                position: absolute;
+                top: -6px;
+                right: -6px;
+                width: 16px;
+                height: 16px;
+                border-radius: 50%;
+                background: #C0C4CC;
+                color: #fff;
+                font-size: 12px;
+                line-height: 16px;
+                text-align: center;
+                cursor: pointer;
+              }
+            }
+          }
+        }
+
+        .photo_input {
+          display: none;
+        }
+
+        .dock_actions {
+          display: flex;
+          align-items: center;
+          gap: 16px;
+
+          .el-button {
+            height: 36px;
+            padding: 0 14px;
+            border-radius: 4px;
+            line-height: 34px;
+            margin:0;
+            &.is-disabled,
+            &.is-disabled:hover,
+            &.is-disabled:focus {
+              background: #F3F3F3;
+              color: #C0C4CC;
+              border: 1px solid #C0C4CC;
+            }
+          }
+
+          .photo_btn {
+            :deep(span) {
+              display: inline-flex;
+              align-items: center;
+              gap: 4px;
+            }
+
+            .camera_icon {
+              width: 16px;
+              height: 16px;
+              display: block;
+            }
+          }
+
+          .nav_btn {
+            color: #2E64FA;
+            border-color: #2E64FA;
+            background: #fff;
+
+            &.is-disabled,
+            &.is-disabled:hover,
+            &.is-disabled:focus {
+              background: #F3F3F3;
+              color: #C0C4CC;
+              border: 1px solid #C0C4CC;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 576 - 0
src/views/teachingHomework/homeworkTask/list.vue

@@ -0,0 +1,576 @@
+<template>
+  <div class="teaching_homework_page">
+    <!-- 外层容器限制宽度,避免公共组件 width:100% 在 flex 下撑出横向滚动 -->
+    <div class="subject_filter_wrap">
+      <TargetSubject />
+    </div>
+
+    <div class="homework_panel">
+      <div class="filter_bar">
+        <el-radio-group v-model="submitStatus" class="status_radio" @change="onStatusChange">
+          <el-radio-button :label="0">未提交</el-radio-button>
+          <el-radio-button :label="1">已提交</el-radio-button>
+        </el-radio-group>
+        <el-input
+          v-model="keyword"
+          class="search_keyword"
+          clearable
+          placeholder="请输入关键字搜索"
+          @keyup.enter.native="handleSearch"
+          @clear="handleSearch">
+          <el-button slot="append" icon="el-icon-search" @click="handleSearch"></el-button>
+        </el-input>
+      </div>
+
+      <div class="homework_list" v-loading="loading">
+        <div v-if="!loading && list.length === 0" class="empty_tip no_content_data">
+          <span>暂无数据</span>
+        </div>
+        <div
+          v-for="item in list"
+          :key="item.id || item.homeworkId"
+          class="homework_card">
+          <div class="card_left">
+            <div class="card_title_row">
+              <span
+                class="subject_badge"
+                :style="{ background: getSubjectColor(item.subjectName) }">
+                {{ item.subjectName || subjectName || '科目' }}
+              </span>
+              <span class="card_title" :title="item.homeworkName || item.taskName">
+                {{ item.homeworkName || item.taskName }}
+              </span>
+            </div>
+            <div class="card_meta">
+              <template v-if="submitStatus === 0">
+                <span>开始时间:{{ item.startTime || '-' }}</span>
+                <span>结束时间:{{ item.endTime || '-' }}</span>
+              </template>
+              <span v-else>更新时间:{{ item.updateTime || item.endTime || '-' }}</span>
+              <span>试题数量:{{ item.questionNum != null ? item.questionNum : '-' }}</span>
+              <span>
+                状态:
+                <em :class="submitStatus === 0 ? 'status_todo' : 'status_done'">
+                  {{ submitStatus === 0 ? '未提交' : '已提交' }}
+                </em>
+              </span>
+            </div>
+          </div>
+          <div class="card_right">
+            <el-button
+              v-if="submitStatus === 0"
+              type="primary"
+              class="enter_btn"
+              @click="handleEnter(item)">
+              进入作答
+            </el-button>
+            <template v-else>
+              <el-button class="view_answer_btn" @click="handleViewAnswer(item)">
+                查看作答
+              </el-button>
+              <el-button
+                type="primary"
+                class="view_analysis_btn"
+                :disabled="isAnalysisDisabled(item)"
+                @click="handleViewAnalysis(item)">
+                查看分析
+              </el-button>
+            </template>
+          </div>
+        </div>
+      </div>
+
+      <div class="pager_row" v-if="total > 0">
+        <span class="total_text">共 {{ total }} 条数据</span>
+        <el-pagination
+          background
+          layout="prev, pager, next"
+          :current-page.sync="pageNum"
+          :page-size="pageSize"
+          :total="total"
+          @current-change="fetchList" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { mapState } from 'vuex';
+import TargetSubject from '@/views/targetImprove/components/TargetSubject.vue';
+
+export default {
+  name: 'TeachingHomeworkList',
+  components: { TargetSubject },
+  data() {
+    return {
+      submitStatus: 0, // 0未提交 1已提交
+      keyword: '',
+      loading: false,
+      list: [],
+      pageNum: 1,
+      pageSize: 10,
+      total: 0,
+      // TODO: 接口就绪后删除假数据
+      mockList: [
+        {
+          id: 1,
+          subjectName: '语文',
+          homeworkName: '9月11日作业任务',
+          startTime: '26-08-10 19:00:00',
+          endTime: '26-08-10 19:00:00',
+          questionNum: 12,
+          submitStatus: 0,
+        },
+        {
+          id: 2,
+          subjectName: '数学',
+          homeworkName: '9月11日作业任务',
+          startTime: '26-08-10 19:00:00',
+          endTime: '26-08-10 19:00:00',
+          questionNum: 12,
+          submitStatus: 0,
+        },
+        {
+          id: 3,
+          subjectName: '英语',
+          homeworkName: '9月12日单元练习',
+          startTime: '26-08-11 08:00:00',
+          endTime: '26-08-12 18:00:00',
+          questionNum: 20,
+          submitStatus: 0,
+        },
+        {
+          id: 4,
+          subjectName: '物理',
+          homeworkName: '力学专项训练',
+          startTime: '26-08-09 10:00:00',
+          endTime: '26-08-15 22:00:00',
+          questionNum: 15,
+          submitStatus: 0,
+        },
+        {
+          id: 5,
+          subjectName: '语文',
+          homeworkName: '9月11日作业任务',
+          startTime: '26-08-10 19:00:00',
+          endTime: '26-08-10 19:00:00',
+          updateTime: '26-08-10 19:00:00',
+          questionNum: 12,
+          submitStatus: 1,
+          analysisDisabled: false,
+        },
+        {
+          id: 6,
+          subjectName: '语文',
+          homeworkName: '9月11日作业任务',
+          startTime: '26-08-10 19:00:00',
+          endTime: '26-08-10 19:00:00',
+          updateTime: '26-08-10 19:00:00',
+          questionNum: 12,
+          submitStatus: 1,
+          analysisDisabled: true,
+        },
+        {
+          id: 7,
+          subjectName: '语文',
+          homeworkName: '9月11日作业任务',
+          startTime: '26-08-10 19:00:00',
+          endTime: '26-08-10 19:00:00',
+          updateTime: '26-08-10 19:00:00',
+          questionNum: 12,
+          submitStatus: 1,
+          analysisDisabled: false,
+        },
+        {
+          id: 8,
+          subjectName: '语文',
+          homeworkName: '文言文阅读专项',
+          startTime: '26-08-05 19:00:00',
+          endTime: '26-08-11 19:00:00',
+          updateTime: '26-08-11 19:00:00',
+          questionNum: 12,
+          submitStatus: 0,
+        },
+        {
+          id: 9,
+          subjectName: '语文',
+          homeworkName: '9月11日作业任务',
+          startTime: '26-08-04 19:00:00',
+          endTime: '26-08-10 19:00:00',
+          updateTime: '26-08-10 19:00:00',
+          questionNum: 12,
+          submitStatus: 1,
+          analysisDisabled: false,
+        },
+        {
+          id: 10,
+          subjectName: '英语',
+          homeworkName: '完形填空强化练习',
+          startTime: '26-08-03 19:00:00',
+          endTime: '26-08-09 19:00:00',
+          questionNum: 14,
+          submitStatus: 0,
+        },
+        {
+          id: 11,
+          subjectName: '语文',
+          homeworkName: '9月11日作业任务',
+          startTime: '26-08-02 19:00:00',
+          endTime: '26-08-08 19:00:00',
+          updateTime: '26-08-08 19:00:00',
+          questionNum: 12,
+          submitStatus: 1,
+          analysisDisabled: true,
+        },
+        {
+          id: 12,
+          subjectName: '语文',
+          homeworkName: '9月11日作业任务',
+          startTime: '26-08-01 19:00:00',
+          endTime: '26-08-07 19:00:00',
+          updateTime: '26-08-07 19:00:00',
+          questionNum: 12,
+          submitStatus: 1,
+          analysisDisabled: false,
+        },
+      ],
+    };
+  },
+  computed: {
+    ...mapState('target', ['subjectCode', 'subjectName']),
+  },
+  watch: {
+    subjectCode() {
+      this.pageNum = 1;
+      this.fetchList();
+    },
+  },
+  mounted() {
+    this.fetchList();
+  },
+  methods: {
+    getSubjectColor(name) {
+      const subjectName = name || this.subjectName || '';
+      return this.$global.getSubjectsName(subjectName, null, 'color');
+    },
+    onStatusChange() {
+      this.pageNum = 1;
+      this.fetchList();
+    },
+    handleSearch() {
+      this.pageNum = 1;
+      this.fetchList();
+    },
+    handleRefresh() {
+      this.fetchList();
+    },
+    // TODO: 接口就绪后改为真实请求
+    fetchList() {
+      this.loading = true;
+      const keyword = (this.keyword || '').trim();
+      let rows = this.mockList.filter(item => item.submitStatus === this.submitStatus);
+      if (keyword) {
+        rows = rows.filter(item => {
+          const name = item.homeworkName || item.taskName || '';
+          return name.indexOf(keyword) !== -1;
+        });
+      }
+      this.total = rows.length;
+      const start = (this.pageNum - 1) * this.pageSize;
+      this.list = rows.slice(start, start + this.pageSize);
+      this.$nextTick(() => {
+        this.loading = false;
+      });
+    },
+    handleEnter(item) {
+      sessionStorage.setItem('teachingHomeworkAnswer', JSON.stringify({
+        homework: {
+          id: item.id,
+          subjectName: item.subjectName || this.subjectName || '',
+          homeworkName: item.homeworkName || item.taskName || '',
+          teacherName: item.teacherName || '王老师',
+          className: item.className || '1班',
+          questionNum: item.questionNum,
+          endTime: item.endTime || '',
+        },
+        currentIndex: 0,
+        answers: {},
+        remainSeconds: 10 * 60,
+      }));
+      this.$router.push('/teachingHomework/answer');
+    },
+    // 查看分析是否置灰(假数据用 analysisDisabled,接口可改为 analysisStatus 等)
+    isAnalysisDisabled(item) {
+      return !!(item && item.analysisDisabled);
+    },
+    handleViewAnswer(item) {
+      sessionStorage.setItem('teachingHomeworkViewAnswer', JSON.stringify({
+        homework: {
+          id: item.id,
+          subjectName: item.subjectName || this.subjectName || '',
+          homeworkName: item.homeworkName || item.taskName || '',
+          teacherName: item.teacherName || '王老师',
+          className: item.className || '1班',
+          questionNum: item.questionNum,
+        },
+        currentIndex: 0,
+      }));
+      this.$router.push('/teachingHomework/viewAnswer');
+    },
+    handleViewAnalysis(item) {
+      if (this.isAnalysisDisabled(item)) return;
+      sessionStorage.setItem('teachingHomeworkAnalysis', JSON.stringify({
+        homework: {
+          id: item.id,
+          subjectName: item.subjectName || this.subjectName || '',
+          homeworkName: item.homeworkName || item.taskName || '',
+          teacherName: item.teacherName || '王老师',
+          className: item.className || '1班',
+          questionNum: item.questionNum,
+        },
+      }));
+      this.$router.push('/teachingHomework/analysis');
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.teaching_homework_page {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  height: 100%;
+  min-width: 0;
+  box-sizing: border-box;
+  overflow: hidden;
+}
+
+.subject_filter_wrap {
+  flex-shrink: 0;
+  width: 100%;
+  min-width: 0;
+  box-sizing: border-box;
+  overflow: hidden;
+}
+
+.homework_panel {
+  flex: 1;
+  min-width: 0;
+  min-height: 0;
+  display: flex;
+  flex-direction: column;
+  background: #fff;
+  border-radius: 10px;
+  padding: 16px 20px 12px;
+  box-sizing: border-box;
+  overflow: hidden;
+}
+
+.filter_bar {
+  flex-shrink: 0;
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  margin-bottom: 20px;
+
+  .status_radio {
+    flex-shrink: 0;
+
+    :deep(.el-radio-button__inner) {
+      height: 36px;
+      line-height: 36px;
+      padding: 0 10px;
+      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;
+    }
+  }
+
+  .search_keyword {
+    width: 240px;
+
+    :deep(.el-input__inner) {
+      height: 36px;
+      line-height: 36px;
+    }
+
+    :deep(.el-input-group__append) {
+      background: #F5F7FA;
+      color: #909399;
+      padding: 0 14px;
+    }
+
+    :deep(.el-input-group__append .el-button) {
+      margin: 0;
+      padding: 0;
+      border: none;
+      background: transparent;
+      color: #909399;
+    }
+  }
+}
+
+.homework_list {
+  flex: 1;
+  min-height: 0;
+  overflow-x: hidden;
+  overflow-y: auto;
+  padding-right: 10px;
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+
+.homework_card {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 20px;
+  padding: 12px;
+  border: 1px solid #E9EBEF;
+  border-radius: 6px  ;
+  box-sizing: border-box;
+  background: #F8FAFE;
+
+  &:last-child {
+    margin-bottom: 0;
+  }
+}
+
+.card_left {
+  flex: 1;
+  min-width: 0;
+}
+
+.card_title_row {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  margin-bottom: 10px;
+}
+
+.subject_badge {
+  flex-shrink: 0;
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  height: 25px;
+  padding: 0 8px;
+  border-radius: 4px;
+  color: #fff;
+  font-size: 14px;
+  line-height: 1;
+}
+
+.card_title {
+  font-size: 16px;
+  font-weight: 550;
+  color: #333;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.card_meta {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 16px;
+  font-size: 14px;
+  color: #999;
+
+  em {
+    font-style: normal;
+  }
+
+  .status_todo {
+    color: #E6A23C;
+  }
+
+  .status_done {
+    color: #67C23A;
+  }
+}
+
+.card_right {
+  flex-shrink: 0;
+  display: flex;
+  align-items: center;
+  gap: 10px;
+
+  .enter_btn,
+  .view_answer_btn,
+  .view_analysis_btn {
+    height: 36px;
+    padding: 0 14px;
+    border-radius: 4px;
+    line-height: 34px;
+    margin: 0;
+  }
+
+  .view_answer_btn {
+    color: #2E64FA;
+    border-color: #2E64FA;
+    background: #fff;
+
+    &:hover,
+    &:focus {
+      color: #2E64FA;
+      border-color: #2E64FA;
+      background: #fff;
+    }
+  }
+
+  .view_analysis_btn {
+    &.is-disabled,
+    &.is-disabled:hover,
+    &.is-disabled:focus {
+      background: #F5F7FA;
+      color: #C0C4CC;
+      border-color: #EBEEF5;
+    }
+  }
+}
+
+.pager_row {
+  flex-shrink: 0;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-top: 16px;
+  padding-top: 8px;
+
+  .total_text {
+    font-size: 14px;
+    color: #666;
+  }
+}
+
+.empty_tip {
+  min-height: 320px;
+  height: 100%;
+  width: 100%;
+  border-radius: 8px;
+
+  span {
+    margin-top: 18%;
+  }
+}
+</style>

+ 885 - 0
src/views/teachingHomework/homeworkTask/viewAnswer.vue

@@ -0,0 +1,885 @@
+<template>
+  <!-- 查看作答页。路由固定为 /teachingHomework/viewAnswer,不使用地址栏参数 -->
+  <div class="homework_view_answer" 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 class="meta_row">
+          <span>布置教师:{{ homework.teacherName }}</span>
+          <span>试题数量:{{ questions.length }}</span>
+        </div>
+      </div>
+    </div>
+
+    <!-- 主体:左侧试题序号,右侧题干/作答/解析 -->
+    <div class="answer_body">
+      <div class="question_nav">
+        <div class="nav_title">试题列表</div>
+        <div class="nav_group" v-for="group in questionGroups" :key="group.type">
+          <div class="group_name">{{ group.name }}</div>
+          <div class="num_grid">
+            <button
+              v-for="item in group.list"
+              :key="item.id"
+              type="button"
+              class="num_btn"
+              :class="numClass(item)"
+              @click="selectQuestion(item.index)">
+              {{ item.index + 1 }}
+            </button>
+          </div>
+        </div>
+      </div>
+
+      <div class="question_panel" v-if="currentQuestion">
+        <!-- 锚点 tabs:点击滚动到对应区块 -->
+        <div class="section_tabs">
+          <span
+            v-for="tab in sectionTabs"
+            :key="tab.key"
+            class="section_tab"
+            :class="{ active: activeSection === tab.key }"
+            @click="scrollToSection(tab.key)">
+            {{ tab.label }}
+          </span>
+        </div>
+
+        <!-- 右侧内容区固定高度,内部滚动 -->
+        <div class="question_scroll" ref="scrollBox" @scroll="onScroll">
+          <!-- 题干 -->
+          <div class="content_section" ref="sectionStem" data-section="stem">
+            <div class="q_tags">
+              <span class="q_no">{{ currentQuestion.index + 1 }}</span>
+              <span class="q_type">{{ currentQuestion.typeName }}</span>
+              <span class="q_level">{{ currentQuestion.difficulty }}</span>
+            </div>
+            <div class="q_stem">{{ currentQuestion.stem }}</div>
+            <div v-if="currentQuestion.type === 'single'" class="q_options">
+              <div class="q_option" v-for="opt in currentQuestion.options" :key="opt.key">
+                <span class="opt_key">{{ opt.key }}.</span>
+                <span>{{ opt.text }}</span>
+              </div>
+            </div>
+          </div>
+
+          <!-- 我的作答(只读) -->
+          <div class="content_section" ref="sectionAnswer" data-section="answer">
+            <div class="block_box my_answer_box">
+              <div class="dock_answer">
+                <span class="dock_label">我的作答:</span>
+                <span v-if="isSingle" class="dock_sub_no">(1)</span>
+                <el-radio-group
+                  v-if="isSingle"
+                  class="choice_radio"
+                  :value="currentUserAnswer"
+                  disabled>
+                  <el-radio
+                    v-for="opt in currentQuestion.options"
+                    :key="opt.key"
+                    :label="opt.key">
+                    {{ opt.key }}
+                  </el-radio>
+                </el-radio-group>
+                <div v-else class="photo_list">
+                  <div
+                    class="photo_item"
+                    v-for="(img, imgIndex) in currentPhotos"
+                    :key="imgIndex">
+                    <img :src="img" alt="" @click="previewPhoto(imgIndex)">
+                  </div>
+                  <span v-if="!currentPhotos.length" class="empty_answer">暂无作答图片</span>
+                </div>
+              </div>
+            </div>
+          </div>
+
+          <!-- 答案解析 -->
+          <div class="content_section" ref="sectionAnalysis" data-section="analysis">
+            <div class="block_box analysis_box">
+              <div class="analysis_row">
+                <span class="analysis_label">【知识点】</span>
+                <div class="knowledge_tags">
+                  <span
+                    class="knowledge_tag"
+                    v-for="(tag, idx) in currentQuestion.knowledgePoints"
+                    :key="idx">
+                    {{ tag }}
+                  </span>
+                </div>
+              </div>
+              <div class="analysis_row">
+                <span class="analysis_label">【 答 案 】</span>
+                <div class="analysis_content">
+                  <span v-if="isSingle" class="correct_answer">{{ currentQuestion.correctAnswer }}</span>
+                  <div class="analysis_text">{{ currentQuestion.analysis }}</div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 底部操作区固定 -->
+        <div class="view_dock">
+          <el-button class="ai_btn" @click="handleAiExplain">AI讲题</el-button>
+          <div class="dock_actions">
+            <el-button
+              class="nav_btn"
+              :disabled="currentQuestion.index === 0"
+              @click="prevQuestion">
+              上一题
+            </el-button>
+            <el-button
+              class="nav_btn"
+              :disabled="isLastQuestion"
+              @click="nextQuestion">
+              下一题
+            </el-button>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <el-image-viewer
+      v-if="imgVisible"
+      :z-index="9999"
+      :initial-index="previewIndex"
+      :url-list="currentPhotos"
+      :on-close="closePhotoPreview" />
+  </div>
+</template>
+
+<script>
+import ElImageViewer from 'element-ui/packages/image/src/image-viewer';
+
+// 查看作答状态存 sessionStorage,刷新仍停留本页,地址栏不带参数
+const STORAGE_KEY = 'teachingHomeworkViewAnswer';
+
+/**
+ * 临时试题 + 作答/解析数据,接口就绪后替换
+ * type: single 选择题 / photo 解答题
+ */
+function buildQuestions() {
+  const options = [
+    { key: 'A', text: '①⑤⑨' },
+    { key: 'B', text: '①③⑦' },
+    { key: 'C', text: '④⑤⑥' },
+    { key: 'D', text: '③④⑧' },
+  ];
+  const knowledgePoints = ['作家作品', '分析体裁特征和表现手法', '阅读理解'];
+  const list = [];
+  for (let i = 1; i <= 11; i += 1) {
+    list.push({
+      id: i,
+      type: 'single',
+      typeName: '选择题',
+      groupName: '单选题',
+      difficulty: '容易',
+      stem: `${i}、下列各句中,加点词的意义和用法相同的一组是`,
+      options,
+      userAnswer: i % 4 === 0 ? 'B' : 'A',
+      correctAnswer: 'D',
+      knowledgePoints,
+      analysis: '本题考查文言实词的意义和用法。结合语境分析各选项中加点词含义,正确答案为 D。',
+    });
+  }
+  for (let i = 12; i <= 16; i += 1) {
+    list.push({
+      id: i,
+      type: 'photo',
+      typeName: '解答题',
+      groupName: '解答题',
+      difficulty: '中等',
+      stem: `${i}、阅读材料,结合课文内容简要分析,并写出你的理解。`,
+      userAnswer: [],
+      correctAnswer: '',
+      knowledgePoints,
+      analysis: '解答本题需先通读材料,抓住关键信息,结合课文主旨作答,注意条理清晰。',
+    });
+  }
+  return list;
+}
+
+export default {
+  name: 'TeachingHomeworkViewAnswer',
+  components: {
+    ElImageViewer,
+  },
+  data() {
+    return {
+      homework: null,
+      questions: buildQuestions(),
+      currentIndex: 0,
+      activeSection: 'stem',
+      sectionTabs: [
+        { key: 'stem', label: '题干' },
+        { key: 'answer', label: '我的作答' },
+        { key: 'analysis', label: '答案解析' },
+      ],
+      imgVisible: false,
+      previewIndex: 0,
+      scrollLock: false,
+    };
+  },
+  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');
+    },
+    questionGroups() {
+      const groups = [];
+      this.questions.forEach((item, index) => {
+        let group = groups.find(g => g.type === item.type);
+        if (!group) {
+          group = { type: item.type, name: item.groupName, list: [] };
+          groups.push(group);
+        }
+        group.list.push({ ...item, index });
+      });
+      return groups;
+    },
+    currentQuestion() {
+      const item = this.questions[this.currentIndex];
+      if (!item) return null;
+      return { ...item, index: this.currentIndex };
+    },
+    isSingle() {
+      return !!(this.currentQuestion && this.currentQuestion.type === 'single');
+    },
+    isLastQuestion() {
+      return this.currentIndex >= this.questions.length - 1;
+    },
+    currentUserAnswer() {
+      if (!this.currentQuestion || !this.isSingle) return '';
+      return this.currentQuestion.userAnswer || '';
+    },
+    currentPhotos() {
+      if (!this.currentQuestion || this.isSingle) return [];
+      const val = this.currentQuestion.userAnswer;
+      return Array.isArray(val) ? val : [];
+    },
+  },
+  created() {
+    this.restoreState();
+  },
+  beforeDestroy() {
+    this.persistState();
+  },
+  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;
+      this.currentIndex = Number(cache.currentIndex) || 0;
+      if (this.currentIndex < 0 || this.currentIndex >= this.questions.length) {
+        this.currentIndex = 0;
+      }
+      this.activeSection = 'stem';
+      this.$nextTick(() => {
+        const box = this.$refs.scrollBox;
+        if (box) box.scrollTop = 0;
+      });
+    },
+    persistState() {
+      if (!this.homework) return;
+      sessionStorage.setItem(STORAGE_KEY, JSON.stringify({
+        homework: this.homework,
+        currentIndex: this.currentIndex,
+      }));
+    },
+    numClass(item) {
+      return {
+        current: item.index === this.currentIndex,
+      };
+    },
+    selectQuestion(index) {
+      this.currentIndex = index;
+      this.activeSection = 'stem';
+      this.persistState();
+      this.$nextTick(() => {
+        const box = this.$refs.scrollBox;
+        if (box) box.scrollTop = 0;
+      });
+    },
+    prevQuestion() {
+      if (this.currentIndex <= 0) return;
+      this.selectQuestion(this.currentIndex - 1);
+    },
+    nextQuestion() {
+      if (this.currentIndex >= this.questions.length - 1) return;
+      this.selectQuestion(this.currentIndex + 1);
+    },
+    // 点击 tab 滚动到对应区块
+    scrollToSection(key) {
+      const map = {
+        stem: this.$refs.sectionStem,
+        answer: this.$refs.sectionAnswer,
+        analysis: this.$refs.sectionAnalysis,
+      };
+      const target = map[key];
+      const box = this.$refs.scrollBox;
+      if (!target || !box) return;
+      this.activeSection = key;
+      this.scrollLock = true;
+      box.scrollTop = target.offsetTop - 8;
+      setTimeout(() => {
+        this.scrollLock = false;
+      }, 300);
+    },
+    // 滚动时同步高亮 tab
+    onScroll() {
+      if (this.scrollLock) return;
+      const box = this.$refs.scrollBox;
+      if (!box) return;
+      const scrollTop = box.scrollTop;
+      const sections = [
+        { key: 'stem', el: this.$refs.sectionStem },
+        { key: 'answer', el: this.$refs.sectionAnswer },
+        { key: 'analysis', el: this.$refs.sectionAnalysis },
+      ];
+      let current = 'stem';
+      sections.forEach(item => {
+        if (item.el && item.el.offsetTop - 24 <= scrollTop) {
+          current = item.key;
+        }
+      });
+      this.activeSection = current;
+    },
+    previewPhoto(index) {
+      this.previewIndex = index;
+      this.imgVisible = true;
+    },
+    closePhotoPreview() {
+      this.imgVisible = false;
+    },
+    handleAiExplain() {
+      this.$message.info('AI讲题待对接');
+    },
+    goBack() {
+      this.persistState();
+      this.$router.push('/teachingHomework/list');
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+/* 布局、间距、字号、颜色与 answer.vue 保持一致 */
+.homework_view_answer {
+  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;
+      }
+    }
+  }
+
+  .answer_body {
+    flex: 1;
+    min-height: 0;
+    display: flex;
+    gap: 10px;
+    margin-top: 10px;
+    overflow: hidden;
+
+    .question_nav {
+      flex-shrink: 0;
+      width: 240px;
+      height: 100%;
+      background: #fff;
+      border-radius: 10px;
+      box-sizing: border-box;
+      overflow-y: auto;
+
+      .nav_title {
+        font-size: 14px;
+        font-weight: 550;
+        color: #333;
+        padding: 10px;
+        border-bottom: 1px solid #EBEEF5;
+      }
+
+      .nav_group {
+        padding: 10px;
+
+        & + .nav_group {
+          margin-top: 24px;
+        }
+
+        .group_name {
+          font-size: 14px;
+          color: #333;
+          font-weight: 550;
+          padding-bottom: 12px;
+        }
+
+        .num_grid {
+          display: grid;
+          grid-template-columns: repeat(5, 32px);
+          gap: 15px;
+
+          /* 查看作答:未选中灰框,当前题绿底白字 */
+          .num_btn {
+            width: 32px;
+            height: 32px;
+            padding: 0;
+            border-radius: 4px;
+            border: 1px solid #2BC644;
+            background: #fff;
+            color: #2BC644;
+            font-size: 14px;
+            cursor: pointer;
+
+            &.current {
+              background: #2BC644;
+              border-color: #2BC644;
+              color: #fff;
+            }
+          }
+        }
+      }
+    }
+
+    .question_panel {
+      flex: 1;
+      min-width: 0;
+      height: 100%;
+      display: flex;
+      flex-direction: column;
+      background: #fff;
+      border-radius: 10px;
+      overflow: hidden;
+
+      .section_tabs {
+        flex-shrink: 0;
+        display: flex;
+        align-items: center;
+        gap: 32px;
+        padding:16px;
+        box-sizing: border-box;
+
+        .section_tab {
+          position: relative;
+          height: 28px;
+          line-height: 28px;
+          font-size: 14px;
+          color: #909399;
+          cursor: pointer;
+          user-select: none;
+
+          &.active {
+            color: #2E64FA;
+            font-weight: 550;
+
+            &::after {
+              content: '';
+              position: absolute;
+              left: 0;
+              right: 0;
+              bottom: 0;
+              height: 3px;
+              background: #2E64FA;
+              border-radius: 1px;
+            }
+          }
+        }
+      }
+
+      .question_scroll {
+        flex: 1;
+        min-height: 0;
+        overflow-y: auto;
+        padding: 16px;
+        box-sizing: border-box;
+
+        .content_section {
+          margin-bottom: 20px;
+        }
+
+        .q_tags {
+          display: flex;
+          align-items: center;
+          gap: 8px;
+          margin-bottom: 16px;
+
+          .q_no {
+            display: inline-flex;
+            align-items: center;
+            justify-content: center;
+            min-width: 24px;
+            height: 24px;
+            padding: 0 6px;
+            background: #F3F3F3;
+            border-radius: 2px;
+            font-weight: 550;
+            font-size: 14px;
+            color: #333333;
+            box-sizing: border-box;
+          }
+
+          .q_type {
+            font-weight: 550;
+            font-size: 14px;
+            color: #333333;
+          }
+
+          .q_level {
+            font-weight: 400;
+            font-size: 14px;
+            color: #90CB75;
+            margin-left: 8px;
+          }
+        }
+
+        .q_stem {
+          font-size: 15px;
+          line-height: 1.7;
+          color: #333;
+          margin-bottom: 16px;
+        }
+
+        .q_options {
+          .q_option {
+            display: flex;
+            gap: 6px;
+            margin-bottom: 10px;
+            font-size: 14px;
+            color: #333;
+            line-height: 1.6;
+
+            .opt_key {
+              flex-shrink: 0;
+            }
+          }
+        }
+
+        .block_box {
+          padding: 14px 16px;
+          background: #F8F8F9;
+          border-radius: 10px;
+          border: 1px solid #DCDFE6;
+          box-sizing: border-box;
+        }
+
+        .dock_answer {
+          display: flex;
+          align-items: center;
+          gap: 16px;
+          min-height: 36px;
+
+          .dock_label,
+          .dock_sub_no {
+            flex-shrink: 0;
+            display: inline-flex;
+            align-items: center;
+            height: 20px;
+            line-height: 20px;
+            font-size: 14px;
+            color: #333;
+          }
+
+          .choice_radio {
+            display: inline-flex;
+            align-items: center;
+            height: 20px;
+            gap: 36px;
+
+            :deep(.el-radio) {
+              margin-right: 0 !important;
+              height: 20px;
+              line-height: 20px;
+              display: inline-flex !important;
+              align-items: center;
+            }
+
+            :deep(.el-radio__input) {
+              display: inline-flex;
+              align-items: center;
+              height: 16px;
+              line-height: 16px;
+            }
+
+            :deep(.el-radio__inner) {
+              width: 16px;
+              height: 16px;
+              border: 1px solid #DCDFE6;
+              background: #fff;
+
+              &::after {
+                width: 8px;
+                height: 8px;
+                background-color: #2E64FA;
+                border-radius: 50%;
+              }
+            }
+
+            :deep(.el-radio__input.is-checked .el-radio__inner) {
+              border-color: #2E64FA;
+              background: #fff;
+            }
+
+            :deep(.el-radio__input.is-disabled .el-radio__inner) {
+              background: #fff;
+              border-color: #DCDFE6;
+              cursor: default;
+            }
+
+            :deep(.el-radio__input.is-disabled.is-checked .el-radio__inner) {
+              border-color: #2E64FA;
+              background: #fff;
+            }
+
+            :deep(.el-radio__input.is-disabled + span.el-radio__label) {
+              color: #333;
+              cursor: default;
+            }
+
+            :deep(.el-radio__label) {
+              display: inline-flex;
+              align-items: center;
+              height: 20px;
+              padding-left: 8px;
+              font-size: 14px;
+              color: #333;
+              line-height: 20px;
+            }
+          }
+
+          .photo_list {
+            display: flex;
+            flex-wrap: wrap;
+            align-items: center;
+            gap: 10px;
+            min-width: 0;
+
+            .photo_item {
+              width: 64px;
+              height: 64px;
+              border-radius: 4px;
+              overflow: hidden;
+              background: #fff;
+
+              img {
+                width: 100%;
+                height: 100%;
+                object-fit: cover;
+                display: block;
+                cursor: zoom-in;
+              }
+            }
+
+            .empty_answer {
+              font-size: 14px;
+              color: #999;
+            }
+          }
+        }
+
+        .analysis_box {
+          .analysis_row {
+            display: flex;
+            align-items: flex-start;
+            gap: 10px;
+
+            & + .analysis_row {
+              margin-top: 14px;
+            }
+          }
+
+          .analysis_label {
+            flex-shrink: 0;
+            font-size: 14px;
+            font-weight: 550;
+            color: #333;
+            line-height: 28px;
+          }
+
+          .knowledge_tags {
+            display: flex;
+            flex-wrap: wrap;
+            gap: 8px;
+
+            .knowledge_tag {
+              display: inline-flex;
+              align-items: center;
+              height: 28px;
+              padding: 0 10px;
+              border: 1px solid #2E64FA;
+              border-radius: 4px;
+              color: #2E64FA;
+              font-size: 13px;
+              background: #fff;
+              box-sizing: border-box;
+            }
+          }
+
+          .analysis_content {
+            flex: 1;
+            min-width: 0;
+
+            .correct_answer {
+              display: inline-block;
+              margin-right: 8px;
+              font-size: 14px;
+              font-weight: 550;
+              color: #333;
+              line-height: 28px;
+            }
+
+            .analysis_text {
+              margin-top: 6px;
+              font-size: 14px;
+              line-height: 1.7;
+              color: #666;
+            }
+          }
+        }
+      }
+
+      .view_dock {
+        flex-shrink: 0;
+        display: flex;
+        align-items: center;
+        justify-content: flex-start;
+        gap: 16px;
+        padding: 16px;
+        background: #fff;
+        box-sizing: border-box;
+
+        .ai_btn {
+          height: 36px;
+          padding: 0 14px;
+          border-radius: 4px;
+          border: none;
+          background: linear-gradient( 270deg, #6659FF 0%, #9A57FF 60.38%, #F292FF 100%);
+          color: #fff;
+
+          // &:hover,
+          // &:focus {
+          //   background: #847BE6;
+          //   color: #fff;
+          //   opacity: 0.92;
+          // }
+        }
+
+        .dock_actions {
+          display: flex;
+          align-items: center;
+          gap: 16px;
+
+          .nav_btn {
+            height: 36px;
+            padding: 0 14px;
+            border-radius: 4px;
+            line-height: 34px;
+            color: #2E64FA;
+            border-color: #2E64FA;
+            background: #fff;
+            margin:0;
+            &.is-disabled,
+            &.is-disabled:hover,
+            &.is-disabled:focus {
+              background: #F3F3F3;
+              color: #C0C4CC;
+              border: 1px solid #C0C4CC;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 31 - 0
src/views/teachingHomework/index.vue

@@ -0,0 +1,31 @@
+<template>
+  <div class="container_layout">
+    <div class="layout_header">
+      <Header />
+    </div>
+    <div class="layout_main teaching_homework_main">
+      <router-view></router-view>
+    </div>
+  </div>
+</template>
+<script>
+import Header from "../layout/components/Header.vue";
+export default {
+  name: "TeachingHomework",
+  components: { Header },
+};
+</script>
+<style lang="scss" scoped>
+.container_layout {
+  overflow-x: hidden;
+}
+
+.teaching_homework_main {
+  box-sizing: border-box;
+  padding: 10px;
+  width: 100%;
+  height: calc(100% - 60px);
+  overflow-x: hidden;
+  overflow-y: hidden;
+}
+</style>