Browse Source

Merge branch 'dev' of https://gogs.k12100.net/liuquanxin/bigDataStudent into dev

lm 4 days ago
parent
commit
7a053ba432

+ 3 - 0
src/http/api.js

@@ -2,8 +2,11 @@ import user from './api/user'//个人中心 相关接口
 import reportStudent from "./api/reportStudent";
 import personalProfile from "./api/personalProfile";
 
+import target from "./api/target";//靶向提升接口文档
+
 export default {
     user,  
     reportStudent,
     personalProfile,
+    target
 }

+ 16 - 0
src/http/api/target.js

@@ -0,0 +1,16 @@
+// 靶向提升相关接口api
+import { get, post} from '../common/http';
+import base from '../common/base';
+const targetImprove = {
+
+  //获取顶部头部筛选
+  getTargetHeaderData(data) {
+    return get( base.prefix+ `/api/v1/studentTarge/target_top`, data)
+  },
+
+
+
+
+}
+
+export default targetImprove

+ 1 - 1
src/router/index.js

@@ -48,7 +48,7 @@ let constantRoutes = [
             },
             {
                 path: "consolidate",
-                component: () => import("@/views/targetImprove/practice/index.vue"),
+                component: () => import("@/views/targetImprove/consolidate/index.vue"),
                 meta: {
                     title: "错题巩固",
                 },

+ 3 - 1
src/store/index.js

@@ -7,13 +7,15 @@ import user from './modules/user'
 import analysis from './modules/analysis'
 import report from "./modules/report";
 import question from "./modules/question";
+import target from './modules/target';//靶向提升模块
 Vue.use(Vuex)
 const store = new Vuex.Store({
   modules: {
     user,
     analysis,
     report,
-    question
+    question,
+    target
   },
   getters,
   plugins: [createPersistedState()]

+ 25 - 0
src/store/modules/target.js

@@ -0,0 +1,25 @@
+// store变量
+let state = {
+  subjectCode:'',//顶部筛选的科目
+
+};
+// 同步
+let mutations = {
+  //设置筛选数据  
+  SetFilterSubjectCode(state, data) {
+    state.subjectCode = data;
+  },
+
+
+};
+// 异步
+let actions = {
+
+};
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions,
+};

+ 10 - 11
src/styles/common.scss

@@ -109,13 +109,8 @@ li {
 }
 
 
-.page_target
+.target_item
 {
-
-  width: 100%;
-  height: auto;
-  .target_item
-  {
     width: 100%;
     height:auto;
     box-sizing: border-box;
@@ -123,12 +118,16 @@ li {
     border-radius: 10px;
     margin-bottom: 10px;
     padding: 10px;
-  }
+}
+
+
+
+.page_target
+{
+  width: 100%;
+  height: 100%;
+  overflow:hidden;
 
-  .target_filter
-  {
-    padding: 5px 10px;
-  }
 
   .target_module
   {

+ 1 - 1
src/views/targetImprove/components/SideBar.vue

@@ -48,7 +48,7 @@ export default {
 
 
 
-    this.activeMenu();
+    // this.activeMenu();
   },
   methods: {
     // activeMenu() {

+ 81 - 0
src/views/targetImprove/components/TargetSubject.vue

@@ -0,0 +1,81 @@
+<template>
+   <div class="target_filter">
+      <FiltersItem :filtersData="filtersData"  @selectItem="ChangeFilters"/>
+    </div>
+</template>
+<script>
+import FiltersItem from "@/components/FiltersItem_ruoyan.vue"//筛选组件
+
+export default {
+  components: {
+    FiltersItem,
+  },
+  computed: {
+   
+  },
+  data() {
+    return {
+      activeMenuName: "/targetImprove",
+      filtersData:[
+        {
+          name:'科目名称',
+          value:'1',
+          list:[],
+        },
+      ],
+      allMenuList:[],//总菜单
+    };
+  },
+  watch: {
+
+  },
+  created() {
+    
+  },
+  mounted() {
+    this.GetHeaderData();//获取头部数据
+  },
+  methods: {
+
+    // 获取头部数据
+    GetHeaderData()
+    {
+       
+        this.$api.target.getTargetHeaderData().then(res => {
+          if(res.code == 200)
+          {
+            console.log("打印顶部筛选数据",res.data);
+            this.filtersData[0].list = (res.data.subjectData || []).map(item => {
+                return {
+                    label: item.subjectName,   // 后端显示文本,例如 "化学"
+                    value: item.subjectCode // 
+                }
+            })
+
+          }
+        });
+    },
+    
+    // 筛选组件回调
+    ChangeFilters(e)
+    {
+      this.filtersData[e.index].value = e.value;
+      console.log("打印筛选组件数据",e);
+      this.$store.commit('target/SetFilterSubjectCode', e.value)
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+.target_filter
+{
+    width: 100%;
+    height:48px;
+    background: #fff;
+    border-radius: 10px;
+    padding-left: 20px;
+    padding-right: 10px;
+    padding-top: 2px;
+    margin-bottom: 10px;
+}
+</style>

+ 29 - 1
src/views/targetImprove/consolidate/index.vue

@@ -1,9 +1,37 @@
 <template>
-  <div class="">
+  <div class="page_target">
+    <TargetSubject></TargetSubject>
+    <!-- 错题巩固 -->
 
   </div>
 </template>
 <script>
+import TargetSubject from "@/views/targetImprove/components/TargetSubject.vue"//顶部筛选科目组件
+
 export default {
+  components: {
+    TargetSubject,
+
+  },
+  data() {
+    return {
+
+    }
+  },
+  computed:{
+
+  },
+  mounted() 
+  {
+
+  },
+  methods: { 
+    
+
+
+  }
 };
 </script>
+<style lang="scss" scoped>
+
+</style>

+ 132 - 43
src/views/targetImprove/practice/index.vue

@@ -1,25 +1,39 @@
 <template>
   <div class="page_target">
-    <div class="target_item target_filter">
-      <FiltersItem :filtersData="filtersData"  @selectItem="ChangeFilters"/>
-    </div>
-    <div class="target_item">
-      选择练习知识点 
+    <TargetSubject></TargetSubject>
+    <div class="target_item target_step">
+      <div class="step_item" :class="stepIndex==1?'step_item_cur':''" @click="ChangeStep(1)">
+        <div class="span_number">1</div>选择练习知识点 
+      </div>
+      <div class="step_line">
+        ------------------------------
+      </div>
+      <div class="step_item" :class="stepIndex==2?'step_item_cur':''" @click="ChangeStep(2)">
+        <div class="span_number">2</div>知识点练习作答 
+      </div>
+      <div class="step_line">
+        ------------------------------
+      </div>
+      <div class="step_item" :class="stepIndex==3?'step_item_cur':''" @click="ChangeStep(3)">
+        <div class="span_number">3</div>查看练习结果 
+      </div>
+
     </div>
     <!-- 知识点图谱 -->
-    <knowledgeGraph></knowledgeGraph>
-    <PracticeAnswer></PracticeAnswer>
-    <PracticeResults></PracticeResults>
+    <knowledgeGraph v-if="stepIndex==1"></knowledgeGraph>
+    <PracticeAnswer v-if="stepIndex==2"></PracticeAnswer>
+    <PracticeResults v-if="stepIndex==3"></PracticeResults>
   </div>
 </template>
 <script>
-import FiltersItem from "@/components/FiltersItem_ruoyan.vue"//筛选组件
+import TargetSubject from "@/views/targetImprove/components/TargetSubject.vue"//顶部筛选科目组件
 import knowledgeGraph from "@/views/targetImprove/practice/knowledgeGraph.vue"//知识点图谱
 import PracticeAnswer from "@/views/targetImprove/practice/practiceAnswer.vue"//练习作答
 import PracticeResults from "@/views/targetImprove/practice/practiceResults.vue"//练习结果
+import { mapState } from 'vuex'
 export default {
   components: {
-    FiltersItem,
+    TargetSubject,
     knowledgeGraph,
     PracticeAnswer,
     PracticeResults
@@ -27,53 +41,128 @@ export default {
   data() {
     return {
       userInfo: {},
-      filtersData:[
-        {
-          name:'科目名称',
-          value:'1',
-          list:[
-            {
-              label:'语文',
-              value:"1",
-            },
-            {
-              label:'数学',
-              value:"2",
-            },
-            {
-              label:'英语',
-              value:"3",
-            },
-            {
-              label:'物理',
-              value:"4",
-            },
-            {
-              label:'化学',
-              value:"5",
-            },
-            
-          ],
-        },
-      ]
+
+      stepIndex:1,//步骤
     }
   },
   computed:{
-
+    ...mapState('target', ['subjectCode'])
   },
+  watch:{
+    // 监听store里面subjectCode变化
+    subjectCode(newVal, oldVal){
+      console.log('科目编码改变了', newVal, oldVal)
+      // ========= 业务逻辑写这里 =========
+      // 例如:切换科目,重置步骤、重新请求知识点图谱数据
+      
+    }
+  },
+
   mounted() 
   {
 
   },
   methods: { 
-    // 筛选组件回调
-    ChangeFilters(e)
+    
+
+    // 步骤切换
+    ChangeStep(index)
     {
-      this.filtersData[e.index].value = e.value;
+      this.stepIndex = index;
     },
   }
 };
 </script>
 <style lang="scss" scoped>
+.target_step
+{
+  display: flex;
+  justify-content: flex-start;
+  align-items: center;
+  padding: 10px 20px;
+  height: 78px;
+
+  .step_item
+  {
+    width: 260px;
+    height: 50px;
+    border-radius: 10px 10px 10px 10px;
+    border: 1px solid #DCDFE6;
+    line-height: 50px;
 
+    font-weight: 550;
+    font-size: 16px;
+    color: #333333;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    gap: 14px;
+    cursor: pointer;
+    .span_number
+    {
+      width: 30px;
+      height: 30px;
+      line-height: 30px;
+      text-align: center;
+      border-radius: 50%;
+      border: 1px solid #2E64FA;
+      background: #EBF0FF;
+
+      font-weight: 550;
+      font-size: 16px;
+      color: #2E64FA;
+    }
+
+  }
+
+  .step_item_cur
+  {
+   
+    border-radius: 10px 10px 10px 10px;
+    border: 1px solid #2E64FA;
+    line-height: 50px;
+    background: #2E64FA10;
+    font-weight: 550;
+    font-size: 16px;
+    color: #2E64FA;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    gap: 14px;
+    cursor: pointer;
+    .span_number
+    {
+      width: 30px;
+      height: 30px;
+      line-height: 30px;
+      text-align: center;
+      border-radius: 50%;
+      border: 1px solid #2E64FA;
+      background: #2E64FA;
+
+      font-weight: 550;
+      font-size: 16px;
+      color: #fff;
+    }
+  }
+
+  .step_line
+  {
+    // min-width:200px;
+    width:calc(50% - 390px);
+    height:10px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    line-height: 10px;
+    font-weight: 550;
+    font-size: 16px;
+    color: #2E64FA;
+    letter-spacing: 0.1em;
+    overflow: hidden;
+    padding: 0 10px;
+    padding-top: 5px;
+
+  }
+}
 </style>

+ 385 - 0
src/views/targetImprove/practice/knowledgeGraph.vue

@@ -0,0 +1,385 @@
+<template>
+  <div class="knowledge_graph" ref="knowledgeGraph" :style="{ maxHeight: containerHeight + 'px' }">
+    <!-- 环形扩散图组件 -->
+    <circular-diffusion
+      :activeView="activeView"
+      :subjectName="subjectName"
+      :subjectScoreRate="subjectScoreRate"
+      :tableData="displayTableData"
+      :fatalVulnerability="displayFatalVulnerability"
+      :highVulnerability="displayHighVulnerability"
+      :allKnowledgeList="displayAllKnowledgeList"
+      :classLevel="classLevel"
+      :classGroupName="classGroupName"
+      :currentKnowledgeId="currentKnowledgeId"
+      @view-change="handleViewChange"
+      @legend-change="handleLegendChange"
+      @activeTabChange="handleActiveTabChange"
+      @knowledge-item-click="handleKnowledgeItemClick"
+    />
+
+    <!-- 区域展示组件 -->
+    <region
+      :allKnowledgeList="displayAllKnowledgeList"
+      @add-practice="handleAddPractice"
+    />
+  </div>
+</template>
+
+<script>
+import CircularDiffusion from './knowledgeGraph/circularDiffusion.vue';
+import Region from './knowledgeGraph/region.vue';
+
+export default {
+  name: 'KnowledgeGraph',
+  components: {
+    CircularDiffusion,
+    Region
+  },
+  props: {
+    activeView: {
+      type: String,
+      default: 'graph'
+    },
+    subjectName: {
+      type: String,
+      default: '数学知识点'
+    },
+    subjectScoreRate: {
+      type: Number,
+      default: 85
+    },
+    tableData: {
+      type: Array,
+      default: () => []
+    },
+    fatalVulnerability: {
+      type: Array,
+      default: () => []
+    },
+    highVulnerability: {
+      type: Array,
+      default: () => []
+    },
+    allKnowledgeList: {
+      type: Array,
+      default: () => []
+    },
+    classLevel: {
+      type: [String, Number],
+      default: ''
+    },
+    classGroupName: {
+      type: String,
+      default: ''
+    },
+    currentKnowledgeId: {
+      type: [String, Number],
+      default: ''
+    },
+    showRegion: {
+      type: Boolean,
+      default: true
+    }
+  },
+  data() {
+    return {
+      // 容器最大高度
+      containerHeight: 600,
+      // 假数据:表格树形结构数据(用于图表渲染)
+      // knowledgeType: 1=薄弱(红色), 2=良好(黄色), 3=优秀(绿色)
+      // gradeScoreRate: 年级得分率,用于判断节点是否有数据
+      // examNum: 考试次数,questionNum: 题目数量
+      mockTableData: [
+        {
+          knowledgeId: '1',
+          knowledgeName: '函数与方程',
+          knowledgeType: 1,
+          gradeScoreRate: 45,
+          classScoreRate: 45,
+          examNum: 8,
+          questionNum: 120,
+          scoreRateDiff: -5,
+          children: [
+            {
+              knowledgeId: '1-1',
+              knowledgeName: '一元二次方程',
+              knowledgeType: 1,
+              gradeScoreRate: 35,
+              classScoreRate: 35,
+              examNum: 5,
+              questionNum: 60,
+              scoreRateDiff: -10,
+              children: []
+            },
+            {
+              knowledgeId: '1-2',
+              knowledgeName: '函数图像',
+              knowledgeType: 1,
+              gradeScoreRate: 55,
+              classScoreRate: 55,
+              examNum: 3,
+              questionNum: 40,
+              scoreRateDiff: -2,
+              children: []
+            }
+          ]
+        },
+        {
+          knowledgeId: '2',
+          knowledgeName: '几何图形',
+          knowledgeType: 2,
+          gradeScoreRate: 72,
+          classScoreRate: 72,
+          examNum: 4,
+          questionNum: 80,
+          scoreRateDiff: 3,
+          children: [
+            {
+              knowledgeId: '2-1',
+              knowledgeName: '三角形',
+              knowledgeType: 2,
+              gradeScoreRate: 68,
+              classScoreRate: 68,
+              examNum: 2,
+              questionNum: 30,
+              scoreRateDiff: 2,
+              children: []
+            },
+            {
+              knowledgeId: '2-2',
+              knowledgeName: '圆的性质',
+              knowledgeType: 2,
+              gradeScoreRate: 78,
+              classScoreRate: 78,
+              examNum: 2,
+              questionNum: 50,
+              scoreRateDiff: 5,
+              children: []
+            }
+          ]
+        },
+        {
+          knowledgeId: '3',
+          knowledgeName: '概率统计',
+          knowledgeType: 3,
+          gradeScoreRate: 90,
+          classScoreRate: 90,
+          examNum: 1,
+          questionNum: 25,
+          scoreRateDiff: 8,
+          children: [
+            {
+              knowledgeId: '3-1',
+              knowledgeName: '数据分析',
+              knowledgeType: 3,
+              gradeScoreRate: 92,
+              classScoreRate: 92,
+              examNum: 1,
+              questionNum: 15,
+              scoreRateDiff: 10,
+              children: []
+            },
+            {
+              knowledgeId: '3-2',
+              knowledgeName: '概率计算',
+              knowledgeType: 3,
+              gradeScoreRate: 88,
+              classScoreRate: 88,
+              examNum: 1,
+              questionNum: 10,
+              scoreRateDiff: 6,
+              children: []
+            }
+          ]
+        },
+        {
+          knowledgeId: '4',
+          knowledgeName: '数列',
+          knowledgeType: 2,
+          gradeScoreRate: 65,
+          classScoreRate: 65,
+          examNum: 6,
+          questionNum: 90,
+          scoreRateDiff: 1,
+          children: []
+        },
+        {
+          knowledgeId: '5',
+          knowledgeName: '立体几何',
+          knowledgeType: 1,
+          gradeScoreRate: 50,
+          classScoreRate: 50,
+          examNum: 7,
+          questionNum: 100,
+          scoreRateDiff: -3,
+          children: []
+        },
+        {
+          knowledgeId: '6',
+          knowledgeName: '向量',
+          knowledgeType: 2,
+          gradeScoreRate: 82,
+          classScoreRate: 82,
+          examNum: 3,
+          questionNum: 45,
+          scoreRateDiff: 4,
+          children: []
+        },
+        {
+          knowledgeId: '7',
+          knowledgeName: '导数应用',
+          knowledgeType: 3,
+          gradeScoreRate: 95,
+          classScoreRate: 95,
+          examNum: 2,
+          questionNum: 20,
+          scoreRateDiff: 12,
+          children: []
+        },
+        {
+          knowledgeId: '8',
+          knowledgeName: '不等式',
+          knowledgeType: 1,
+          gradeScoreRate: 58,
+          classScoreRate: 58,
+          examNum: 5,
+          questionNum: 75,
+          scoreRateDiff: -1,
+          children: []
+        }
+      ]
+    };
+  },
+  computed: {
+    // 实际显示的表格数据:props为空时使用假数据
+    displayTableData() {
+      if (this.tableData && this.tableData.length > 0) {
+        return this.tableData;
+      }
+      return this.mockTableData;
+    },
+    // 实际显示的零分知识点数据
+    displayFatalVulnerability() {
+      if (this.fatalVulnerability && this.fatalVulnerability.length > 0) {
+        return this.fatalVulnerability;
+      }
+      // 假数据:零分知识点
+      return [
+        { knowledgeId: '1-1', knowledgeName: '一元二次方程', knowledgeType: 1, gradeScoreRate: 35, classScoreRate: 35, examNum: 5, questionNum: 60, scoreRateDiff: -10 },
+        { knowledgeId: '5', knowledgeName: '立体几何', knowledgeType: 1, gradeScoreRate: 50, classScoreRate: 50, examNum: 7, questionNum: 100, scoreRateDiff: -3 },
+        { knowledgeId: '8', knowledgeName: '不等式', knowledgeType: 1, gradeScoreRate: 58, classScoreRate: 58, examNum: 5, questionNum: 75, scoreRateDiff: -1 }
+      ];
+    },
+    // 实际显示的高频错题知识点数据
+    displayHighVulnerability() {
+      if (this.highVulnerability && this.highVulnerability.length > 0) {
+        return this.highVulnerability;
+      }
+      // 假数据:高频错题知识点
+      return [
+        { knowledgeId: '1', knowledgeName: '函数与方程', knowledgeType: 1, gradeScoreRate: 45, classScoreRate: 45, examNum: 8, questionNum: 120, scoreRateDiff: -5 },
+        { knowledgeId: '4', knowledgeName: '数列', knowledgeType: 2, gradeScoreRate: 65, classScoreRate: 65, examNum: 6, questionNum: 90, scoreRateDiff: 1 },
+        { knowledgeId: '2-1', knowledgeName: '三角形', knowledgeType: 2, gradeScoreRate: 68, classScoreRate: 68, examNum: 2, questionNum: 30, scoreRateDiff: 2 },
+        { knowledgeId: '2-2', knowledgeName: '圆的性质', knowledgeType: 2, gradeScoreRate: 78, classScoreRate: 78, examNum: 2, questionNum: 50, scoreRateDiff: 5 }
+      ];
+    },
+    // 实际显示的全部知识点数据
+    displayAllKnowledgeList() {
+      if (this.allKnowledgeList && this.allKnowledgeList.length > 0) {
+        return this.allKnowledgeList;
+      }
+      // 假数据:全部知识点(扁平化结构,用于列表显示和区域展示)
+      return [
+        { knowledgeId: '1', knowledgeName: '函数与方程', knowledgeType: 1, gradeScoreRate: 45, classScoreRate: 45, examNum: 8, questionNum: 120, scoreRateDiff: -5 },
+        { knowledgeId: '1-1', knowledgeName: '一元二次方程', knowledgeType: 1, gradeScoreRate: 35, classScoreRate: 35, examNum: 5, questionNum: 60, scoreRateDiff: -10 },
+        { knowledgeId: '1-2', knowledgeName: '函数图像', knowledgeType: 1, gradeScoreRate: 55, classScoreRate: 55, examNum: 3, questionNum: 40, scoreRateDiff: -2 },
+        { knowledgeId: '2', knowledgeName: '几何图形', knowledgeType: 2, gradeScoreRate: 72, classScoreRate: 72, examNum: 4, questionNum: 80, scoreRateDiff: 3 },
+        { knowledgeId: '2-1', knowledgeName: '三角形', knowledgeType: 2, gradeScoreRate: 68, classScoreRate: 68, examNum: 2, questionNum: 30, scoreRateDiff: 2 },
+        { knowledgeId: '2-2', knowledgeName: '圆的性质', knowledgeType: 2, gradeScoreRate: 78, classScoreRate: 78, examNum: 2, questionNum: 50, scoreRateDiff: 5 },
+        { knowledgeId: '3', knowledgeName: '概率统计', knowledgeType: 3, gradeScoreRate: 90, classScoreRate: 90, examNum: 1, questionNum: 25, scoreRateDiff: 8 },
+        { knowledgeId: '3-1', knowledgeName: '数据分析', knowledgeType: 3, gradeScoreRate: 92, classScoreRate: 92, examNum: 1, questionNum: 15, scoreRateDiff: 10 },
+        { knowledgeId: '3-2', knowledgeName: '概率计算', knowledgeType: 3, gradeScoreRate: 88, classScoreRate: 88, examNum: 1, questionNum: 10, scoreRateDiff: 6 },
+        { knowledgeId: '4', knowledgeName: '数列', knowledgeType: 2, gradeScoreRate: 65, classScoreRate: 65, examNum: 6, questionNum: 90, scoreRateDiff: 1 },
+        { knowledgeId: '5', knowledgeName: '立体几何', knowledgeType: 1, gradeScoreRate: 50, classScoreRate: 50, examNum: 7, questionNum: 100, scoreRateDiff: -3 },
+        { knowledgeId: '6', knowledgeName: '向量', knowledgeType: 2, gradeScoreRate: 82, classScoreRate: 82, examNum: 3, questionNum: 45, scoreRateDiff: 4 },
+        { knowledgeId: '7', knowledgeName: '导数应用', knowledgeType: 3, gradeScoreRate: 95, classScoreRate: 95, examNum: 2, questionNum: 20, scoreRateDiff: 12 },
+        { knowledgeId: '8', knowledgeName: '不等式', knowledgeType: 1, gradeScoreRate: 58, classScoreRate: 58, examNum: 5, questionNum: 75, scoreRateDiff: -1 }
+      ];
+    }
+  },
+  mounted() {
+    // 初始化容器高度
+    this.calculateContainerHeight();
+    // 监听窗口大小变化
+    window.addEventListener('resize', this.calculateContainerHeight);
+  },
+  beforeDestroy() {
+    // 移除窗口大小变化监听
+    window.removeEventListener('resize', this.calculateContainerHeight);
+  },
+  methods: {
+    // 计算容器高度
+    calculateContainerHeight() {
+      if (this.$refs.knowledgeGraph) {
+        // 获取容器距离视口顶部的距离
+        const rect = this.$refs.knowledgeGraph.getBoundingClientRect();
+        // 计算容器最大高度:窗口高度 - 容器距离顶部的距离 - 底部留白
+        const maxHeight = window.innerHeight - rect.top - 20;
+        // 设置最小高度为400px
+        this.containerHeight = Math.max(maxHeight, 400);
+      }
+    },
+    // 处理视图切换
+    handleViewChange(view) {
+      this.$emit('view-change', view);
+    },
+    
+    // 处理图例变化
+    handleLegendChange(data) {
+      this.$emit('legend-change', data);
+    },
+    
+    // 处理tab切换
+    handleActiveTabChange(tab) {
+      this.$emit('activeTabChange', tab);
+    },
+    
+    // 处理知识点点击
+    handleKnowledgeItemClick(data) {
+      this.$emit('knowledge-item-click', data);
+    },
+    
+    // 处理加入练习
+    handleAddPractice(item) {
+      this.$emit('add-practice', item);
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.knowledge_graph {
+  overflow-y: auto;
+  overflow-x: hidden;
+}
+
+/* 自定义滚动条样式 */
+.knowledge_graph::-webkit-scrollbar {
+  width: 6px;
+}
+
+.knowledge_graph::-webkit-scrollbar-track {
+  background: #f1f1f1;
+  border-radius: 3px;
+}
+
+.knowledge_graph::-webkit-scrollbar-thumb {
+  background: #c1c1c1;
+  border-radius: 3px;
+
+  &:hover {
+    background: #a8a8a8;
+  }
+}
+</style>

+ 885 - 0
src/views/targetImprove/practice/knowledgeGraph/circularDiffusion.vue

@@ -0,0 +1,885 @@
+<template>
+  <div class="knowledge_graph">
+    <!-- 教师端样式 -->
+    <div class="knowledge_class">
+      <!-- 图形查看容器 -->
+      <div v-if="activeView === 'graph'" class="graph_content">
+        <!-- 左侧图表 -->
+        <div class="chart_container">
+          <!-- 顶部容器:图例和视图切换按钮 -->
+          <div class="top_container">
+            <!-- 自定义图例 -->
+            <div class="legend">
+              <div class="legend_item" :class="{ 'selected': selectedLegend.weak }" @click="toggleLegend('weak')">
+                <span class="legend_dot weak"></span>
+                <span class="legend_text">{{ mode === 'student' && activeComparison ? '薄弱(得分率差值<-10%)'
+                  : '薄弱(0%≤得分率<60%)' }}</span>
+              </div>
+              <div class="legend_item" :class="{ 'selected': selectedLegend.good }" @click="toggleLegend('good')">
+                <span class="legend_dot good"></span>
+                <span class="legend_text">
+                  {{ mode === 'student' && activeComparison ? '持平(-10%≤得分率差值≤10%)' : '良好(60%≤得分率< 85 %) ' }}</span>
+              </div>
+              <div class="legend_item" :class="{ 'selected': selectedLegend.excellent }"
+                @click="toggleLegend('excellent')">
+                <span class="legend_dot excellent"></span>
+                <span class="legend_text">{{ mode === 'student' && activeComparison ? '优势(得分率差值>10%)' : '优秀(85%≤得分率)'
+                }}</span>
+              </div>
+            </div>
+          </div>
+          <div ref="chart" class="chart" style="width: 100%;"></div>
+        </div>
+
+        <!-- 右侧容器 -->
+        <div class="knowledge_right_container">
+          <!-- 右侧容器 -->
+          <div class="knowledge_list">
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+
+// 引入echarts
+import * as echarts from 'echarts';
+
+export default {
+  name: 'CircularDiffusion',
+  props: {
+    studentName: {
+      type: String,
+      default: ''
+    },
+    mode: {
+      type: String,
+      default: 'class',
+      validator: (value) => {
+        return ['class', 'student'].includes(value);
+      }
+    },
+    activeView: {
+      type: String,
+      default: 'graph',
+      validator: (value) => {
+        return ['graph', 'list'].includes(value);
+      }
+    },
+    subjectName: {
+      type: String,
+      default: '知识点'
+    },
+    subjectScoreRate: {
+      type: Number,
+      default: 0
+    },
+    tableData: {
+      type: Array,
+      default: () => []
+    },
+    fatalVulnerability: {
+      type: Array,
+      default: () => []
+    },
+    highVulnerability: {
+      type: Array,
+      default: () => []
+    },
+    allKnowledgeList: {
+      type: Array,
+      default: () => []
+    },
+    isRecordKnowledge: {
+      type: Boolean,
+      default: false
+    },
+    studentKnowledgeLayering: {
+      type: String,
+      default: ''
+    },
+    classLevel: {
+      type: [String, Number],
+      default: ''
+    },
+    classGroupName: {
+      type: String,
+      default: ''
+    },
+    historicalChangeData: {
+      type: Object,
+      default: () => ({
+        examNames: [],
+        scores: [],
+        trendData: [],
+        personalScoreRate: []
+      })
+    },
+    historicalChangeLoading: {
+      type: Boolean,
+      default: false
+    },
+    currentKnowledgeId: {
+      type: [String, Number],
+      default: ''
+    },
+    scoreRateTypes: {
+      type: Array,
+      default: () => []
+    },
+    comparisonOptions: {
+      type: Array,
+      default: null
+    },
+    isPush: {
+      type: Boolean,
+      default: false
+    },
+  },
+  computed: {
+  },
+  data() {
+    return {
+      chart: null,
+      _isMounted: false,
+      _isFirstRender: true,
+      activeComparison: null,
+      selectedLegend: {
+        weak: true,
+        good: true,
+        excellent: true
+      },
+      legendNumbers: [1, 2, 3],
+      selectedIndex: 0,
+      selectedKnowledgeId: '',
+      chartRefreshKey: 0,
+      showKnowledgePrompt: this.isRecordKnowledge,
+    };
+  },
+  mounted() {
+    this._isMounted = true;
+    if (this.activeView === 'graph') {
+      this.$nextTick(() => {
+        this.initChart();
+      });
+    }
+  },
+  beforeDestroy() {
+    this._isMounted = false;
+    if (this.chart) {
+      this.chart.dispose();
+    }
+  },
+  watch: {
+    activeView(newView) {
+      if (newView === 'graph') {
+        this.$nextTick(() => {
+          this.initChart();
+        });
+      }
+    },
+    tableData: {
+      deep: true,
+      handler() {
+        if (this.chart) {
+          this.$nextTick(() => {
+            this.updateChart();
+          });
+        }
+      }
+    },
+    currentKnowledgeId: {
+      handler(newVal) {
+        this.selectedKnowledgeId = newVal;
+        if (this.activeView === 'graph' && this.chart) {
+          this.updateChart();
+        }
+      }
+    },
+    isRecordKnowledge: {
+      handler(newVal) {
+        this.showKnowledgePrompt = newVal;
+      }
+    },
+    scoreRateTypes: {
+      deep: true,
+      handler(newVal) {
+        const val = Array.isArray(newVal) ? newVal : [];
+        this.selectedLegend.weak = val.length === 0 || val.includes(1);
+        this.selectedLegend.good = val.length === 0 || val.includes(2);
+        this.selectedLegend.excellent = val.length === 0 || val.includes(3);
+        this.legendNumbers = val.length === 0 ? [1, 2, 3] : [...val].sort((a, b) => a - b);
+        if (this.activeView === 'graph' && this.chart) {
+          this.updateChart();
+        }
+      }
+    },
+  },
+  methods: {
+    setDefaultSelection() {
+      let targetList = [];
+      if (this.allKnowledgeList && this.allKnowledgeList.length > 0) {
+        targetList = this.allKnowledgeList;
+      } else if (this.highVulnerability && this.highVulnerability.length > 0) {
+        targetList = this.highVulnerability;
+      } else if (this.fatalVulnerability && this.fatalVulnerability.length > 0) {
+        targetList = this.fatalVulnerability;
+      }
+      
+      if (targetList.length > 0) {
+        this.selectedIndex = 0;
+        this.selectedKnowledgeId = targetList[0].knowledgeId;
+        
+        // 更新图表高亮状态
+        if (this.chart) {
+          const vm = this;
+          const generateTreeData = (data, level = 0, overrideKnowledgeId = null) => {
+            const currentKnowledgeId = overrideKnowledgeId !== null ? overrideKnowledgeId : vm.selectedKnowledgeId;
+            return data.map(item => {
+              const scoreRate = vm.classLevel == 2 || vm.mode == 'student' ? item.classScoreRate : item.gradeScoreRate;
+              const hasData = scoreRate !== null && scoreRate !== undefined;
+              const isMatched = String(item.knowledgeId) === String(currentKnowledgeId);
+              let symbolSize = 5;
+              if (isMatched) {
+                symbolSize = 25;
+              } else if (hasData) {
+                symbolSize = 10;
+              }
+              const getNodeLevel = (knowledgeType) => {
+                if (knowledgeType == 3) {
+                  return 'excellent';
+                } else if (knowledgeType == 2) {
+                  return 'good';
+                } else {
+                  return 'weak';
+                }
+              };
+              const nodeLevel = getNodeLevel(item.knowledgeType);
+              const shouldShow = !hasData || vm.selectedLegend[nodeLevel];
+              const opacity = shouldShow ? 1 : 0;
+              const node = {
+                name: item.knowledgeName,
+                symbolSize: symbolSize,
+                value: scoreRate,
+                itemStyle: {
+                  color: hasData ? vm.getNodeColor(item.knowledgeType) : '#BFC1C7',
+                  opacity: opacity
+                },
+                lineStyle: {
+                  opacity: shouldShow ? 1 : 0
+                },
+                hasData: hasData,
+                knowledgeId: item.knowledgeId
+              };
+              if (item.children && item.children.length > 0) {
+                node.children = generateTreeData(item.children, level + 1, overrideKnowledgeId);
+              }
+              return node;
+            });
+          };
+          
+          this.chart.setOption({
+            series: [{
+              data: [
+                {
+                  name: this.subjectName,
+                  symbolSize: 5,
+                  value: this.subjectScoreRate,
+                  itemStyle: {
+                    color: '#BFC1C7',
+                    opacity: 1
+                  },
+                  hasData: false,
+                  children: generateTreeData(this.tableData, 0, this.selectedKnowledgeId)
+                }
+              ]
+            }]
+          }, {
+            animation: true,
+            animationDuration: 300
+          });
+        }
+      }
+    },
+    switchView(view) {
+      this.$emit('view-change', view);
+    },
+    classIsGroup() {
+      if (this.classLevel == 1) {
+        return true;
+      } else
+        return false;
+    },
+    getNodeColor(knowledgeType) {
+      const type = knowledgeType;
+      if (type == 3) {
+        return '#3BA272';
+      } else if (type == 2) {
+        return '#FAC858';
+      } else {
+        return '#EE6666';
+      }
+    },
+    initChart() {
+      try {
+        if (!this.$refs.chart) {
+          console.error('图表容器不存在');
+          return;
+        }
+        this.createChart();
+      }
+      catch (error) {
+        console.error('图表初始化失败:', error);
+      }
+    },
+    createChart() {
+      try {
+        if (!this.$refs.chart) {
+          console.error('图表容器不存在');
+          return;
+        }
+        if (this.chart) {
+          this.chart.dispose();
+        }
+        this.chart = echarts.init(this.$refs.chart);
+        const vm = this;
+        const getNodeLevel = (knowledgeType) => {
+          if (knowledgeType == 3) {
+            return 'excellent';
+          } else if (knowledgeType == 2) {
+            return 'good';
+          } else {
+            return 'weak';
+          }
+        };
+        const generateTreeData = (data, level = 0, overrideKnowledgeId = null) => {
+          const currentKnowledgeId = overrideKnowledgeId !== null ? overrideKnowledgeId : vm.selectedKnowledgeId;
+          return data.map(item => {
+            const scoreRate = vm.classLevel == 2 || vm.mode == 'student' ? item.classScoreRate : item.gradeScoreRate;
+            const hasData = scoreRate !== null && scoreRate !== undefined;
+            const isMatched = String(item.knowledgeId) === String(currentKnowledgeId);
+            let symbolSize = 5;
+            if (isMatched) {
+              symbolSize = 25;
+            } else if (hasData) {
+              symbolSize = 10;
+            }
+            const nodeLevel = getNodeLevel(item.knowledgeType);
+            const shouldShow = !hasData || vm.selectedLegend[nodeLevel];
+            const opacity = shouldShow ? 1 : 0;
+            const node = {
+              name: item.knowledgeName,
+              symbolSize: symbolSize,
+              value: scoreRate,
+              itemStyle: {
+                color: hasData ? vm.getNodeColor(item.knowledgeType) : '#BFC1C7',
+                opacity: opacity
+              },
+              lineStyle: {
+                opacity: shouldShow ? 1 : 0
+              },
+              hasData: hasData,
+              knowledgeId: item.knowledgeId
+            };
+            if (item.children && item.children.length > 0) {
+              node.children = generateTreeData(item.children, level + 1, overrideKnowledgeId);
+            }
+            return node;
+          });
+        };
+        const countOutermostNodes = (data) => {
+          let count = 0;
+          const traverse = (nodes) => {
+            nodes.forEach(node => {
+              if (node.children && node.children.length > 0) {
+                traverse(node.children);
+              } else {
+                count++;
+              }
+            });
+          };
+          traverse(data);
+          return count;
+        };
+        const outermostCount = countOutermostNodes(this.tableData);
+        const getOptimalZoom = () => {
+          let zoom = 1.05;
+          return zoom;
+        };
+        const getOptimalRadius = () => {
+          if (outermostCount <= 5) {
+            return ['20%', '65%'];
+          }
+          return ['12%', '75%'];
+        };
+        const option = {
+          tooltip: {
+            formatter: (params) => {
+              if (params.treePathInfo && params.treePathInfo.length === 1) {
+                return params.name;
+              }
+              const hasData = params.data && params.data.hasData;
+              if (hasData) {
+                return `${params.name}<br/>得分率: ${params.value !== null && params.value !== undefined ? params.value : '0'}%`;
+              } else {
+                return params.name;
+              }
+            }
+          },
+          animationDurationUpdate: 1500,
+          animationEasingUpdate: 'quinticInOut',
+          series: [
+            {
+              type: 'tree',
+              layout: 'radial',
+              symbol: 'circle',
+              initialTreeDepth: 999,
+              expandAndCollapse: false,
+              orient: 'radial',
+              roam: false,
+              left: '8%',
+              right: '8%',
+              top: '8%',
+              bottom: '8%',
+              radius: getOptimalRadius(),
+              nodeScaleRatio: 1,
+              layerPadding: outermostCount <= 5 ? [20, 10] : [10, 5],
+              data: [
+                {
+                  name: this.subjectName,
+                  symbolSize: 5,
+                  value: this.subjectScoreRate,
+                  itemStyle: {
+                    color: '#BFC1C7'
+                  },
+                  hasData: false,
+                  children: generateTreeData(this.tableData)
+                }
+              ],
+              label: {
+                show: false
+              },
+              lineStyle: {
+                color: '#ECEEF3',
+                width: 1,
+                type: 'solid'
+              },
+              emphasis: {
+                focus: 'adjacency',
+                lineStyle: {
+                  width: 1
+                }
+              }
+            }
+          ]
+        };
+        this.chart.setOption(option);
+        this.chart.resize();
+        
+        // 设置默认选中项
+        this.$nextTick(() => {
+          this.setDefaultSelection();
+        });
+        
+        this.chart.on('click', (params) => {
+          const hasDataNodeClick = params.data && params.data.hasData;
+          if (hasDataNodeClick) {
+            const allLists = [this.allKnowledgeList, this.highVulnerability, this.fatalVulnerability];
+            let knowledgeItem = null;
+            let targetIndex = -1;
+            let targetList = this.allKnowledgeList;
+            
+            // 直接在所有列表中查找
+            for (const list of allLists) {
+              knowledgeItem = list.find(item => item.knowledgeName === params.name);
+              if (knowledgeItem) {
+                targetList = list;
+                targetIndex = list.findIndex(item => item.knowledgeId === knowledgeItem.knowledgeId);
+                break;
+              }
+            }
+            
+            // 如果没找到,从表格数据中递归查找
+            if (!knowledgeItem) {
+              const findKnowledgeItem = (data) => {
+                for (let item of data) {
+                  if (item.knowledgeName === params.name) {
+                    return item;
+                  }
+                  if (item.children && item.children.length > 0) {
+                    const found = findKnowledgeItem(item.children);
+                    if (found) return found;
+                  }
+                }
+                return null;
+              };
+              knowledgeItem = findKnowledgeItem(this.tableData);
+              if (knowledgeItem) {
+                for (const list of allLists) {
+                  targetIndex = list.findIndex(item => {
+                    return String(item.knowledgeId) === String(knowledgeItem.knowledgeId);
+                  });
+                  if (targetIndex !== -1) {
+                    targetList = list;
+                    break;
+                  }
+                }
+              }
+            }
+            if (knowledgeItem && targetIndex !== -1) {
+              this.$emit('knowledge-item-click', {
+                item: knowledgeItem,
+                index: targetIndex
+              });
+              this.selectedIndex = targetIndex;
+              this.selectedKnowledgeId = knowledgeItem.knowledgeId;
+              this.chart.setOption({
+                series: [{
+                  data: [
+                    {
+                      name: this.subjectName,
+                      symbolSize: 5,
+                      value: this.subjectScoreRate,
+                      itemStyle: {
+                        color: '#BFC1C7',
+                        opacity: 1
+                      },
+                      hasData: false,
+                      children: generateTreeData(this.tableData, 0, this.selectedKnowledgeId)
+                    }
+                  ]
+                }]
+              }, {
+                animation: true,
+                animationDuration: 300
+              });
+            } else {
+              console.log('点击处理调试信息:', {
+                paramsName: params.name,
+                params: params,
+                knowledgeItem: knowledgeItem,
+                targetIndex: targetIndex,
+                targetListLength: targetList.length,
+                targetListSample: targetList.slice(0, 3),
+                tableDataSample: this.tableData.slice(0, 1)
+              });
+            }
+          }
+          else if (isEmptyAreaClick || noDataNodeClick) {
+            this.selectedKnowledgeId = '';
+            const updatedOption = {
+              series: [{
+                data: [
+                  {
+                    name: this.subjectName,
+                    symbolSize: 5,
+                    value: this.subjectScoreRate,
+                    itemStyle: {
+                      color: '#BFC1C7',
+                      opacity: 1
+                    },
+                    isOutermost: false,
+                    children: generateTreeData(this.tableData, 0, '')
+                  }
+                ]
+              }]
+            };
+            this.chart.setOption(updatedOption, {
+              animation: true,
+              animationDuration: 300
+            });
+            this.$emit('knowledge-item-click', {
+              item: null,
+              index: -1
+            });
+          }
+        });
+        window.addEventListener('resize', () => {
+          if (this.chart) {
+            this.chart.resize();
+          }
+        });
+      }
+      catch (error) {
+        console.error('图表创建失败:', error);
+      }
+    },
+    updateChart() {
+      try {
+        if (this.activeView === 'graph' && this.$refs.chart) {
+          this.createChart();
+        }
+      }
+      catch (error) {
+        console.error('图表更新失败:', error);
+      }
+    },
+    reset() {
+      this.selectedIndex = 0;
+      this.updateChart();
+    },
+    toggleLegend(type) {
+      this.selectedLegend[type] = !this.selectedLegend[type];
+      const typeMap = {
+        weak: 1,
+        good: 2,
+        excellent: 3
+      };
+      const number = typeMap[type];
+      if (!this.selectedLegend[type]) {
+        const index = this.legendNumbers.indexOf(number);
+        if (index > -1) {
+          this.legendNumbers.splice(index, 1);
+        }
+      } else {
+        if (!this.legendNumbers.includes(number)) {
+          this.legendNumbers.push(number);
+        }
+        this.legendNumbers.sort((a, b) => a - b);
+      }
+      const hasAllNumbers = this.legendNumbers.includes(1) && this.legendNumbers.includes(2) && this.legendNumbers.includes(3);
+      const dataToSend = hasAllNumbers ? null : this.legendNumbers;
+      this.$emit('legend-numbers-change', dataToSend);
+    },
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.knowledge_graph {
+  background: #FFFFFF;
+  border-radius: 10px;
+  padding: 20px;
+  margin-bottom: 10px;
+  position: relative;
+}
+
+.knowledge_graph,
+.knowledge_graph_student {
+  .knowledge_class {
+    :deep(.vxe-body--row.selected-row),
+    :deep(.selected-row) {
+      background-color: rgba(46, 100, 250, 0.1) !important;
+      color: #2E64FA !important;
+      * {
+        color: #2E64FA !important;
+      }
+      .rate_dot {
+        border-color: rgba(46, 100, 250, 0.1) !important;
+      }
+    }
+
+    .graph_header {
+      margin-bottom: 20px;
+      position: relative;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+
+      .legend {
+        display: flex;
+        gap: 20px;
+
+        .legend_item {
+          display: flex;
+          align-items: center;
+          gap: 5px;
+          cursor: pointer;
+          font-weight: 500;
+
+          &.selected {
+            opacity: 1;
+          }
+
+          &:hover {
+            opacity: 1;
+          }
+
+          .legend_dot {
+            width: 20px;
+            height: 10px;
+            border-radius: 2px;
+            transition: all 0.3s ease;
+
+            &.weak {
+              background: #ccc;
+            }
+
+            &.good {
+              background: #cccccc;
+            }
+
+            &.excellent {
+              background: #ccc;
+            }
+          }
+
+          .legend_text {
+            font-size: 12px;
+            color: #999999;
+            transition: all 0.3s ease;
+          }
+
+          &.selected {
+            .legend_dot.weak {
+              background: #EE6666;
+            }
+
+            .legend_dot.good {
+              background: #FAC858;
+            }
+
+            .legend_dot.excellent {
+              background: #3BA272;
+            }
+
+            .legend_text {
+              color: #333;
+            }
+          }
+        }
+      }
+    }
+
+    .graph_content {
+      display: flex;
+      gap: 20px;
+      position: relative;
+
+      .knowledge_right_container {
+        width: 400px;
+        display: flex;
+        flex-direction: column;
+      }
+
+      .item_chart_container {
+        border: 1px solid #E9EBEF;
+        border-radius: 10px;
+        margin-bottom: 10px;
+      }
+
+      .chart_container {
+        flex: 1;
+        min-width: 0;
+        border-radius: 10px;
+        border: 1px solid #E9EBEF;
+        position: relative;
+        padding: 20px;
+        overflow: hidden;
+
+        .top_container {
+          display: flex;
+          justify-content: space-between;
+          align-items: flex-start;
+          flex-wrap: wrap;
+          gap: 10px;
+          margin-bottom: 10px;
+        }
+
+        .legend {
+          display: flex;
+          gap: 20px;
+          flex-wrap: wrap;
+          flex: 1;
+          min-width: 0;
+
+          .legend_item {
+            display: flex;
+            align-items: center;
+            gap: 5px;
+            cursor: pointer;
+            font-weight: 500;
+
+            &.selected {
+              opacity: 1;
+            }
+
+            &:hover {
+              opacity: 1;
+            }
+
+            .legend_dot {
+              width: 20px;
+              height: 10px;
+              border-radius: 2px;
+              transition: all 0.3s ease;
+
+              &.weak {
+                background: #ccc;
+              }
+
+              &.good {
+                background: #ccc;
+              }
+
+              &.excellent {
+                background: #ccc;
+              }
+            }
+
+            .legend_text {
+              font-size: 12px;
+              color: #999999;
+              transition: all 0.3s ease;
+            }
+
+            &.selected {
+              .legend_dot.weak {
+                background: #EE6666;
+              }
+
+              .legend_dot.good {
+                background: #FAC858;
+              }
+
+              .legend_dot.excellent {
+                background: #3BA272;
+              }
+
+              .legend_text {
+                color: #333;
+              }
+            }
+          }
+        }
+
+        .chart {
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          min-height: 590px;
+        }
+
+      }
+
+      .knowledge_list {
+        width: 100%;
+        height: 410px;
+        overflow-y: auto;
+        padding-right: 10px;
+        position: relative;
+
+        &::-webkit-scrollbar {
+          width: 6px;
+        }
+
+        &::-webkit-scrollbar-track {
+          background: #f1f1f1;
+          border-radius: 3px;
+        }
+
+        &::-webkit-scrollbar-thumb {
+          background: #c1c1c1;
+          border-radius: 3px;
+        }
+
+        &::-webkit-scrollbar-thumb:hover {
+          background: #a8a8a8;
+        }
+      }
+    }
+  }
+}
+</style>

+ 350 - 0
src/views/targetImprove/practice/knowledgeGraph/region.vue

@@ -0,0 +1,350 @@
+<template>
+  <div class="region-container">
+    <!-- 薄弱补强区 -->
+    <div class="region-section weak" v-if="weakArea.length > 0">
+      <div class="region-header">
+        <span class="region-icon">
+          <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" stroke-dasharray="4 2"/>
+          </svg>
+        </span>
+        <span class="region-title">薄弱补强区</span>
+      </div>
+      <div class="region-desc">得分率<60% | 优先补齐短板</div>
+      <div class="knowledge-cards">
+        <div class="knowledge-card" v-for="(item, index) in weakArea" :key="'weak-' + index">
+          <div class="card-header">
+            <span class="knowledge-name">{{ item.knowledgeName }}</span>
+          </div>
+          <div class="card-info">
+            <span class="score-rate">得分率:{{ getScoreRate(item) }}%</span>
+            <span class="wrong-count">错题:{{ item.wrongCount || item.questionNum || 0 }}</span>
+          </div>
+          <div class="card-action">
+            <el-button type="primary" size="small" @click="handleAddPractice(item)">加入练习</el-button>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 能力提升区 -->
+    <div class="region-section good" v-if="goodArea.length > 0">
+      <div class="region-header">
+        <span class="region-icon">
+          <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
+          </svg>
+        </span>
+        <span class="region-title">能力提升区</span>
+      </div>
+      <div class="region-desc">60%≤得分率<85% | 强化练习提升</div>
+      <div class="knowledge-cards">
+        <div class="knowledge-card" v-for="(item, index) in goodArea" :key="'good-' + index">
+          <div class="card-header">
+            <span class="knowledge-name">{{ item.knowledgeName }}</span>
+          </div>
+          <div class="card-info">
+            <span class="score-rate">得分率:{{ getScoreRate(item) }}%</span>
+            <span class="wrong-count">错题:{{ item.wrongCount || item.questionNum || 0 }}</span>
+          </div>
+          <div class="card-action">
+            <el-button type="warning" size="small" @click="handleAddPractice(item)">加入练习</el-button>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 扎实巩固区 -->
+    <div class="region-section excellent" v-if="excellentArea.length > 0">
+      <div class="region-header">
+        <span class="region-icon">
+          <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
+          </svg>
+        </span>
+        <span class="region-title">扎实巩固区</span>
+      </div>
+      <div class="region-desc">得分率≥85% | 定期练习巩固</div>
+      <div class="knowledge-cards">
+        <div class="knowledge-card" v-for="(item, index) in excellentArea" :key="'excellent-' + index">
+          <div class="card-header">
+            <span class="knowledge-name">{{ item.knowledgeName }}</span>
+          </div>
+          <div class="card-info">
+            <span class="score-rate">得分率:{{ getScoreRate(item) }}%</span>
+            <span class="wrong-count">错题:{{ item.wrongCount || item.questionNum || 0 }}</span>
+          </div>
+          <div class="card-action">
+            <el-button type="success" size="small" @click="handleAddPractice(item)">加入练习</el-button>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 冲刺进阶区 -->
+    <div class="region-section advanced" v-if="advancedArea.length > 0">
+      <div class="region-header">
+        <span class="region-icon">
+          <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <path d="M12 2L15 8L22 9L17 14L18 21L12 18L6 21L7 14L2 9L9 8L12 2Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/>
+          </svg>
+        </span>
+        <span class="region-title">冲刺进阶区</span>
+      </div>
+      <div class="region-desc">优于同级学生 | 对标上游冲刺进阶</div>
+      <div class="knowledge-cards">
+        <div class="knowledge-card" v-for="(item, index) in advancedArea" :key="'advanced-' + index">
+          <div class="card-header">
+            <span class="knowledge-name">{{ item.knowledgeName }}</span>
+          </div>
+          <div class="card-info">
+            <span class="score-rate">得分率:{{ getScoreRate(item) }}%</span>
+            <span class="wrong-count">错题:{{ item.wrongCount || item.questionNum || 0 }}</span>
+          </div>
+          <div class="card-action">
+            <el-button type="primary" size="small" @click="handleAddPractice(item)">加入练习</el-button>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'Region',
+  props: {
+    // 全部知识点数据
+    allKnowledgeList: {
+      type: Array,
+      default: () => []
+    }
+  },
+  computed: {
+    // 薄弱补强区:得分率 < 60%
+    weakArea() {
+      return this.allKnowledgeList.filter(item => {
+        const rate = parseFloat(this.getScoreRate(item));
+        return !isNaN(rate) && rate < 60;
+      });
+    },
+    // 能力提升区:60% ≤ 得分率 < 85%
+    goodArea() {
+      return this.allKnowledgeList.filter(item => {
+        const rate = parseFloat(this.getScoreRate(item));
+        return !isNaN(rate) && rate >= 60 && rate < 85;
+      });
+    },
+    // 扎实巩固区:得分率 ≥ 85%
+    excellentArea() {
+      return this.allKnowledgeList.filter(item => {
+        const rate = parseFloat(this.getScoreRate(item));
+        return !isNaN(rate) && rate >= 85;
+      });
+    },
+    // 冲刺进阶区:预留区域,可根据需要扩展
+    advancedArea() {
+      return [];
+    }
+  },
+  methods: {
+    // 获取得分率(优先使用 gradeScoreRate,其次使用 classScoreRate)
+    getScoreRate(item) {
+      return item.gradeScoreRate || item.classScoreRate || item.personalScoreRate || 0;
+    },
+    // 处理加入练习
+    handleAddPractice(item) {
+      this.$emit('add-practice', item);
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.region-container {
+  display: flex;
+  flex-direction: row;
+  gap: 16px;
+  padding: 16px;
+}
+
+.region-section {
+  flex: 1;
+  border-radius: 8px;
+  padding: 16px;
+  background: #FFFFFF;
+  display: flex;
+  flex-direction: column;
+  max-height: 500px;
+
+  &.weak {
+    border: 1px solid rgba(238, 102, 102, 0.5);
+  }
+
+  &.good {
+    border: 1px solid rgba(250, 200, 88, 0.5);
+  }
+
+  &.excellent {
+    border: 1px solid rgba(59, 162, 114, 0.5);
+  }
+
+  &.advanced {
+    border: 1px solid rgba(46, 100, 250, 0.5);
+  }
+}
+
+.region-header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 4px;
+  padding-bottom: 8px;
+  border-bottom: 1px solid #f0f0f0;
+
+  .region-icon {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+
+    svg {
+      width: 18px;
+      height: 18px;
+    }
+  }
+
+  &.weak .region-icon {
+    color: #EE6666;
+  }
+
+  &.good .region-icon {
+    color: #FAC858;
+  }
+
+  &.excellent .region-icon {
+    color: #3BA272;
+  }
+
+  &.advanced .region-icon {
+    color: #2E64FA;
+  }
+
+  .region-title {
+    font-size: 16px;
+    font-weight: 600;
+    color: #333333;
+  }
+}
+
+.region-desc {
+  font-size: 12px;
+  color: #999999;
+  margin-bottom: 12px;
+  padding-top: 4px;
+}
+
+.knowledge-cards {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  overflow-y: auto;
+  flex: 1;
+
+  &::-webkit-scrollbar {
+    width: 4px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: #ddd;
+    border-radius: 2px;
+  }
+}
+
+.knowledge-card {
+  border: 1px solid #ECEEF3;
+  border-radius: 6px;
+  padding: 10px 12px;
+  background: #FFFFFF;
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+  transition: all 0.2s ease;
+
+  &:hover {
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+  }
+
+  .card-header {
+    .knowledge-name {
+      font-size: 13px;
+      font-weight: 500;
+      color: #333333;
+      display: block;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+  }
+
+  .card-info {
+    display: flex;
+    gap: 12px;
+
+    .score-rate,
+    .wrong-count {
+      font-size: 11px;
+      color: #666666;
+    }
+  }
+
+  .card-action {
+    text-align: right;
+
+    .el-button {
+      padding: 6px 12px;
+      font-size: 12px;
+    }
+  }
+}
+
+/* 薄弱补强区卡片样式 */
+.weak .knowledge-card {
+  border-color: rgba(238, 102, 102, 0.3);
+
+  .card-action .el-button {
+    background-color: #EE6666;
+    border-color: #EE6666;
+  }
+}
+
+/* 能力提升区卡片样式 */
+.good .knowledge-card {
+  border-color: rgba(250, 200, 88, 0.3);
+
+  .card-action .el-button {
+    background-color: #FAC858;
+    border-color: #FAC858;
+    color: #333333;
+  }
+}
+
+/* 扎实巩固区卡片样式 */
+.excellent .knowledge-card {
+  border-color: rgba(59, 162, 114, 0.3);
+
+  .card-action .el-button {
+    background-color: #3BA272;
+    border-color: #3BA272;
+  }
+}
+
+/* 冲刺进阶区卡片样式 */
+.advanced .knowledge-card {
+  border-color: rgba(46, 100, 250, 0.3);
+
+  .card-action .el-button {
+    background-color: #2E64FA;
+    border-color: #2E64FA;
+  }
+}
+</style>

+ 13 - 0
src/views/targetImprove/practice/practiceAnswer.vue

@@ -0,0 +1,13 @@
+<template>
+    <div class="practiceAnswer">
+        <div class="title">
+            <span>练习答案</span>
+        </div>
+    </div>
+</template>
+
+<style scoped lang="scss">
+.practiceAnswer {
+  background-color: #ffffff;
+}
+</style>

+ 0 - 227
src/views/targetImprove/practice/practiceResults.vue

@@ -1,227 +0,0 @@
-<!-- 自主练习——查看练习结果 -->
-<template>
-    <div class="target_item">
-        <div class="page_search">
-            <div class="search_content">
-                <div class="content_left">
-                    <div class="grade_title">练习记录</div>
-                </div>
-            </div>
-        </div>
-
-        <FiltersItem :filtersData="filtersData"  @selectItem="ChangeFilters"/>
-      
-        <Table class="practice_table" 
-            :currentPage="currentPage" :pageSize="pageSize" :totalNum="totalNum"
-            :tableData="tableData"  :tableColumns="tableColumns">
-            <template v-slot:gradingStatus="{row}">
-                <TableRowStatus :useStatus="row.gradingStatus"
-                    :statusMsg="[
-                        {
-                            statusFlag:0,
-                            title:'未批阅',
-                            themeClass:'need_summary_icon'
-                        },
-                            {
-                            statusFlag:1,
-                            title:'已批阅',
-                            themeClass:'normal_icon'
-                        },
-                        {
-                            statusFlag:2,
-                            title:'批阅中',
-                            themeClass:'no_release_icon'
-                        },
-                    ]">
-                </TableRowStatus>
-            </template>
-            <template v-slot:action="{row}">
-                <div class="table_row_operation">
-                    <el-button type="text" @click="viewDetails(scope.row)">查看</el-button>
-                </div>
-            </template>
-        </Table>
-    </div>
-
-</template>
-
-<script>
-import FiltersItem from "@/components/FiltersItem_ruoyan.vue"//筛选组件
-import Table from "../components/Table.vue";
-import TableRowStatus from "../components/TableRowStatus.vue";
-export default ({
-    components: {
-        FiltersItem,
-        Table,
-        TableRowStatus
-    },
-    data(){
-        return {
-            currentPage:1,
-            pageSize:20,
-            totalNum:0,
-            tableColumns: [
-                {
-                    name: 'practiceTime',      
-                    label: '练习时间',
-                    minWidth: '180',              
-                    hidden: false,
-                    custom: false,
-                },
-                {
-                    name: 'practiceType',     
-                    label: '练习类型',
-                    minWidth: '120',
-                    align: 'center',         
-                    hidden: false,
-                    custom: false,
-                },
-                {
-                    name: 'knowledgeName',     
-                    label: '知识点名称',
-                    minWidth: '150',           
-                    hidden: false,
-                    custom: false,
-                },
-                {
-                    name: 'questionCount',    
-                    label: '试题数量',
-                    minWidth: '100',
-                    align: 'center',
-                    hidden: false,
-                    custom: false,
-                },
-                {
-                    name: 'gradingStatus',   
-                    label: '批阅状态',
-                    minWidth: '120',
-                    align: 'center',
-                    hidden: false,
-                    custom: true,             
-                },
-                {
-                    name: 'preScoreRate',    
-                    label: '练前得分率',
-                    minWidth: '120',
-                    align: 'center',
-                    hidden: false,
-                    custom: false,            
-                },
-                {
-                    name: 'postScoreRate',    
-                    label: '练后得分率',
-                    minWidth: '120',
-                    align: 'center',
-                    hidden: false,
-                    custom: false,
-                },
-                {
-                    name: 'improvement',       
-                    label: '提升情况',
-                    minWidth: '120',
-                    align: 'center',
-                    hidden: false,
-                    custom: true,              
-                },
-                {
-                    name: 'action',       
-                    label: '操作',
-                    minWidth: '100',
-                    fixed: 'right',   
-                    align: 'center',
-                    hidden: false,
-                    custom: true,   
-                }
-            ],
-            tableData: [
-                {
-                    practiceTime: '2023-08-09 16:00:00',
-                    practiceType: '指定练习',
-                    knowledgeName: '一般词语',
-                    questionCount: 3,
-                    // 状态对象,方便 custom 插槽渲染绿点和文字
-                    gradingStatus: 0,
-                    preScoreRate: '64.37%',
-                    postScoreRate: '54.38%',
-                    // 提升情况对象,方便 custom 插槽判断箭头颜色
-                    improvement: { type: 'up', value: '80.37%' },
-                },
-                {
-                    practiceTime: '2023-08-09 15:30:00',
-                    practiceType: '指定练习',
-                    knowledgeName: '句式仿写',
-                    questionCount: 3,
-                    gradingStatus: 1,
-                    preScoreRate: '91.29%',
-                    postScoreRate: '64.37%',
-                    improvement: { type: 'down', value: '89.36%' },
-                },
-                {
-                    practiceTime: '2023-08-09 14:15:00',
-                    practiceType: '指定练习',
-                    knowledgeName: '现代诗歌',
-                    questionCount: 3,
-                    gradingStatus: 2,
-                    preScoreRate: '64.29%',
-                    postScoreRate: '89.36%',
-                    improvement: { type: 'none', value: '0' },
-                }
-            ],
-            filtersData: [
-                {
-                    name: '时间',
-                    value: 'all', // 默认选中项
-                    list: [
-                        { label: '全部', value: 'all' },
-                        { label: '本周', value: 'this_week' },
-                        { label: '最近1月', value: 'last_1_month' },
-                        { label: '最近3月', value: 'last_3_months' },
-                        { label: '自定义', value: 'custom' }
-                    ]
-                },
-                {
-                    name: '类型',
-                    value: 'all', // 默认选中项
-                    list: [
-                        { label: '全部', value: 'all' },
-                        { label: '指定练习', value: 'assigned_practice' },
-                        { label: '自主练习', value: 'self_practice' }
-                    ]
-                }
-            ]
-        }
-    },
-    methods: {
-        // 筛选组件回调
-        ChangeFilters(e) {
-            // console.log('当前选中的信息',e)
-            this.filtersData[e.index].value = e.value;
-        },
-     },
-    computed:{},
-    mounted() {},
-
-})
-
-
-</script>
-
-
-<style lang="scss" scoped>
-
-.page_search{
-    margin-bottom: 16px;
-}
-.practice_table{
-    margin-top: 2px;
-    ::v-deep .el-table {
-        thead{
-            height: 40px !important;
-        }
-        .el-table__cell {
-            height: 40px;
-        }
-    }
-}
-
-</style>