Преглед изворни кода

修改图谱样式渲染效果

吴朋磊 пре 1 месец
родитељ
комит
0760f6a61d

+ 72 - 27
src/views/targetImprove/practice/knowledgeGraph.vue

@@ -15,10 +15,7 @@
         </div>
         <div class="header_stats">
           <span class="stats_text">
-            你的知识图谱共包含 <span class="stats_num">{{ totalCount }}</span> 个知识点。当前
-            红色薄弱知识点<span class="stats_weak"> {{ weakCount }}</span> 个,
-            黄色待提升知识点<span class="stats_good"> {{ goodCount }}</span> 个,
-            <span class="stats_excellent">坚持练习,把全部知识点都进阶为绿色已掌握状态吧!</span>
+            你的知识图谱共包含<span class="stats_num">{{ totalCount }}</span> 个知识点。当前红色薄弱知识点<span class="stats_weak"> {{ weakCount }}</span> 个,黄色待提升知识点<span class="stats_good"> {{ goodCount }}</span> 个,<span class="stats_excellent">坚持练习,把全部知识点都进阶为绿色已掌握状态吧!</span>
           </span>
         </div>
       </div>
@@ -30,7 +27,6 @@
         :currentKnowledgeId="currentLinkedKnowledgeId || currentKnowledgeId || defaultKnowledgeId"
         :addedKnowledgeIds="addedKnowledgeIds"
         @legend-change="handleLegendChange"
-        @legend-visibility-change="handleLegendVisibilityChange"
         @activeTabChange="handleActiveTabChange"
         @add-practice="handleAddPractice"
         @knowledge-item-click="handleKnowledgeItemClick" />
@@ -38,9 +34,7 @@
       <!-- 区域展示组件 -->
       <region
         :partitionData="partitionData"
-        :addedKnowledgeIds="addedKnowledgeIds"
         :selectedKnowledgeId="currentLinkedKnowledgeId || currentKnowledgeId || defaultKnowledgeId"
-        :legendVisibility="legendVisibility"
         @add-practice="handleAddPractice"
         @item-click="handleRegionItemClick" />
     </div>
@@ -133,9 +127,7 @@ export default {
       // - 点 region 某行条目 → 写这里 → circularDiffusion 读 prop :currentKnowledgeId 放大圆点 + 更新右侧折线表格
       currentLinkedKnowledgeId: '',
       // 假数据:表格树形结构数据(用于无接口数据时兜底)
-      mockTableData: [],
-      // 图例可见状态联动:薄弱(1)/良好(2)/优秀(3) → 控制 region.vue 对应区域显示/隐藏
-      legendVisibility: { 1: true, 2: true, 3: true }
+      mockTableData: []
     };
   },
   computed: {
@@ -215,6 +207,12 @@ export default {
         this.fetchKnowledgeGraph();
         this.fetchKnowledgePartition();
       }
+    },
+    // 监听 vuex store 中 gradesList 变化(TargetSubject 异步接口返回后会更新 store)
+    '$store.state.target.gradesList'(newVal) {
+      if (Array.isArray(newVal) && newVal.length > 0 && this.gradeList.length === 0) {
+        this.loadGradeListFromStorage();
+      }
     }
   },
   mounted() {
@@ -228,6 +226,10 @@ export default {
     this.fetchKnowledgeGraph();
     this.fetchKnowledgePartition();
   },
+  // 当从其他路由切回来时(keep-alive),重新加载年级列表
+  activated() {
+    this.loadGradeListFromStorage();
+  },
   beforeDestroy() {
     // 移除窗口大小变化监听
     window.removeEventListener('resize', this.calculateContainerHeight);
@@ -239,10 +241,18 @@ export default {
      */
     loadGradeListFromStorage() {
       try {
+        let list = [];
+        // 1. 优先从 localStorage 读取
         const raw = localStorage.getItem('student_gradesList');
-        if (!raw) return;
-        const list = JSON.parse(raw);
-        if (!Array.isArray(list)) return;
+        if (raw) {
+          const parsed = JSON.parse(raw);
+          if (Array.isArray(parsed)) list = parsed;
+        }
+        // 2. localStorage 没有则从 vuex store 兜底(store 初始化时也会从 localStorage 读)
+        if (list.length === 0 && this.$store.state.target?.gradesList) {
+          list = this.$store.state.target.gradesList;
+        }
+        if (!Array.isArray(list) || list.length === 0) return;
 
         // 规范化字段:支持 label/value、label/code、name/value 等常见组合
         this.gradeList = list.map(item => ({
@@ -252,7 +262,7 @@ export default {
 
         // 默认只选中最后一个年级
         //   例:[{高一,10},{高二,11}] → currentGradeList = ['11'](仅选中高二)
-        //   同“加入练习接口 gradeCode = gradeList 末尾 value”规则保持一致
+        //   同"加入练习接口 gradeCode = gradeList 末尾 value"规则保持一致
         this.currentGradeList = this.gradeList.length > 0
           ? [this.gradeList[this.gradeList.length - 1].value]
           : [];
@@ -333,6 +343,8 @@ export default {
             improveZone:    this.mapPartitionItem(Array.isArray(d.improveZone)    ? d.improveZone    : []),
             consolidateZone:this.mapPartitionItem(Array.isArray(d.consolidateZone)? d.consolidateZone : [])
           };
+          // 默认选中:薄弱补强区第一条 → 能力提升区第一条 → 扎实巩固区第一条
+          this.setDefaultSelectionFromPartition();
         } else {
           this.resetPartitionEmpty();
         }
@@ -511,6 +523,27 @@ export default {
       this.fetchKnowledgeGraph();
       this.fetchKnowledgePartition();
     },
+
+    /**
+     * 默认选中:薄弱补强区第一条 → 能力提升区第一条 → 扎实巩固区第一条
+     * 将选中 knowledgeId 写入 currentLinkedKnowledgeId,联动 circularDiffusion 圆点高亮
+     */
+    setDefaultSelectionFromPartition() {
+      const conquer = this.partitionData.conquerZone || [];
+      const improve = this.partitionData.improveZone || [];
+      const consolidate = this.partitionData.consolidateZone || [];
+      let firstItem = null;
+      if (conquer.length > 0) {
+        firstItem = conquer[0];
+      } else if (improve.length > 0) {
+        firstItem = improve[0];
+      } else if (consolidate.length > 0) {
+        firstItem = consolidate[0];
+      }
+      if (firstItem && firstItem.knowledgeId) {
+        this.currentLinkedKnowledgeId = String(firstItem.knowledgeId);
+      }
+    },
     // 计算容器高度
     calculateContainerHeight() {
       if (this.$refs.knowledgeGraph) {
@@ -528,18 +561,6 @@ export default {
       this.$emit('legend-change', data);
     },
 
-    // 处理图例可见状态变化:薄弱(1)/良好(2)/优秀(3) 的可见性
-    // 同步更新到 legendVisibility,驱动 region.vue 对应区域显示/隐藏
-    handleLegendVisibilityChange(visibility) {
-      if (!visibility) return;
-      // 保证响应式:直接替换整个对象引用
-      this.legendVisibility = {
-        1: !!visibility[1],
-        2: !!visibility[2],
-        3: !!visibility[3]
-      };
-    },
-
     // 处理tab切换
     handleActiveTabChange(tab) {
       this.$emit('activeTabChange', tab);
@@ -644,9 +665,11 @@ export default {
 
       this.$api.target.addExercise(params).then(res => {
         if (res && (res.code == 200 || res.code == 0)) {
-          // 成功:提示 + 记录 ID(用 $set 保证响应式,region/circularDiffusion 会自动重新渲染并置灰按钮)
           this.$message.success('已加入练习成功');
+          // 1. 记录已加入练习 ID → circularDiffusion 的加入练习按钮置灰
           this.$set(this.addedKnowledgeIds, String(knowledgeId), true);
+          // 2. 从 region 分区列表中移除该条数据
+          this.removeKnowledgeFromPartition(knowledgeId);
         } else {
           this.$message.error(res.msg);
         }
@@ -654,6 +677,28 @@ export default {
         console.error('[KnowledgeGraph] addExercise 接口异常:', err);
         this.$message.error('加入练习失败,请稍后重试');
       });
+    },
+
+    /**
+     * 从分区列表中移除已加入练习的知识点
+     * 遍历三个分区数组,按 knowledgeId 删除匹配项
+     */
+    removeKnowledgeFromPartition(knowledgeId) {
+      const sid = String(knowledgeId);
+      const zones = ['conquerZone', 'improveZone', 'consolidateZone'];
+      let changed = false;
+      for (const zone of zones) {
+        const list = this.partitionData[zone];
+        if (!Array.isArray(list)) continue;
+        const idx = list.findIndex(item => String(item.knowledgeId) === sid);
+        if (idx !== -1) {
+          // 用 splice 保证响应式
+          this.partitionData[zone].splice(idx, 1);
+          changed = true;
+          break;
+        }
+      }
+      return changed;
     }
   }
 };

+ 16 - 66
src/views/targetImprove/practice/knowledgeGraph/circularDiffusion.vue

@@ -215,12 +215,13 @@ export default {
       return item ? (Number(item.isJoin) === 1) : false;
     },
     // 加入练习按钮主题 class(按当前选中圆点的类型匹配颜色)
-    //   与 getCurrentColor() / getNodeColor() 严格对齐:
+    //   动态从 currentKnowledgeItem 取 knowledgeType,不依赖 data 初始值
     //     knowledgeType=3 优秀/扎实巩固 → #3BA272
     //     knowledgeType=2 一般/能力提升 → #FAC858
     //     knowledgeType=1 薄弱          → #EE6666
     addPracticeBtnThemeClass() {
-      const t = Number(this.selectedKnowledgeType);
+      const item = this.currentKnowledgeItem;
+      const t = Number(item ? item.knowledgeType : this.selectedKnowledgeType);
       if (t === 3) return 'btn_theme_excellent';
       if (t === 2) return 'btn_theme_good';
       return 'btn_theme_weak';
@@ -284,7 +285,7 @@ export default {
       legendNumbers: [1, 2, 3],
       selectedIndex: 0,
       selectedKnowledgeId: '',
-      selectedKnowledgeType: 3,
+      selectedKnowledgeType: 1,
       chartRefreshKey: 0,
       showKnowledgePrompt: this.isRecordKnowledge,
       activeTableTab: 'all'
@@ -326,9 +327,9 @@ export default {
         if (this.chart) {
           this.$nextTick(() => {
             this.updateChart();
-            // 数据刷新后,如果父组件没传 currentKnowledgeId,则重新走一次默认选中
-            if (!this.currentKnowledgeId) {
-              this.setDefaultSelection();
+            // 数据刷新后,如果有选中项则重新应用放大效果
+            if (this.selectedKnowledgeId) {
+              this.applySelectedSizeToChart(this.selectedKnowledgeId);
             }
             if (this.lineChart) this.updateLineChart();
           });
@@ -391,9 +392,11 @@ export default {
   methods: {
     // 获取当前颜色(根据选中知识点类型)
     getCurrentColor() {
-      if (this.selectedKnowledgeType === 3) {
+      const item = this.currentKnowledgeItem;
+      const t = Number(item ? item.knowledgeType : this.selectedKnowledgeType);
+      if (t === 3) {
         return '#3BA272';
-      } else if (this.selectedKnowledgeType === 2) {
+      } else if (t === 2) {
         return '#FAC858';
       } else {
         return '#EE6666';
@@ -496,7 +499,7 @@ export default {
         series: [{
           data: seriesData,
           type: 'line',
-          smooth: true,
+          // smooth: true,
           symbol: 'circle',
           symbolSize: 8,
           connectNulls: false,
@@ -1008,11 +1011,6 @@ export default {
         this.chart.setOption(option);
         this.chart.resize();
 
-        // 设置默认选中项
-        this.$nextTick(() => {
-          this.setDefaultSelection();
-        });
-
         this.chart.on('click', (params) => {
           const dataItem = params && params.data;
 
@@ -1163,62 +1161,14 @@ export default {
       const dataToSend = hasAllNumbers ? null : this.legendNumbers;
       this.$emit('legend-numbers-change', dataToSend);
 
-      // 图例可见状态联动:通知父级当前薄弱/良好/优秀三类的可见性
-      // 用于 region.vue 对应区域的数据显示/隐藏
-      this.$emit('legend-visibility-change', {
-        1: this.selectedLegend.weak,       // 薄弱 → knowledgeType=1
-        2: this.selectedLegend.good,       // 良好 → knowledgeType=2
-        3: this.selectedLegend.excellent   // 优秀 → knowledgeType=3
-      });
-
-      // ---- 图例变化后:重算过滤 → 重绘扩散图 → 若选中被隐藏则重选可见节点 ----
+      // ---- 图例变化后:重算过滤 → 重绘扩散图 ----
       this.$nextTick(() => {
-        // 1) 全量重绘(内部 createChart 已经是 calcFilteredTree(tableData) 再生成 children)
         if (this.chart) {
           this.updateChart();
-        }
-
-        // 2) 判断当前选中的节点在新的图例规则下是否"最终可见且有数据"
-        const filteredTree = this.calcFilteredTree(this.tableData);
-        let matchNode = null;
-        const findStack = [...filteredTree];
-        while (findStack.length && !matchNode) {
-          const n = findStack.pop();
-          if (n && this.selectedKnowledgeId &&
-              String(n.knowledgeId) === String(this.selectedKnowledgeId)) {
-            matchNode = n;
-            break;
-          }
-          if (n && Array.isArray(n.children) && n.children.length > 0) {
-            for (let i = 0; i < n.children.length; i++) findStack.push(n.children[i]);
-          }
-        }
-
-        const stillValid = matchNode && matchNode.__finalVisible && matchNode.__finalHasData;
-        if (!stillValid) {
-          // 当前选中的点被隐藏/降级 → 重选一个可见有效叶子(排除当前,避免又选回来)
-          const replacement = this.findFirstVisibleLeafNode(filteredTree, this.selectedKnowledgeId);
-          if (replacement) {
-            this.selectedKnowledgeId = replacement.knowledgeId;
-            this.selectedKnowledgeType = replacement.knowledgeType || 3;
-            // 重新应用圆点放大
-            if (this.chart) this.applySelectedSizeToChart(replacement.knowledgeId);
-            // 同步折线图/表格(computed 依赖 selectedKnowledgeId → selectedExamList 会变,折线这里手动触发)
-            if (this.lineChart) this.updateLineChart();
-            this.$emit('knowledge-item-click', {
-              knowledgeId: replacement.knowledgeId,
-              knowledgeName: replacement.knowledgeName
-            });
-          } else {
-            // 全部关闭了 → 清空
-            this.selectedKnowledgeId = '';
-            this.selectedKnowledgeType = 3;
-            if (this.chart) this.applySelectedSizeToChart('');
-            if (this.lineChart) this.updateLineChart();
+          // 重新应用选中圆点放大效果
+          if (this.selectedKnowledgeId) {
+            this.applySelectedSizeToChart(this.selectedKnowledgeId);
           }
-        } else {
-          // 当前选中仍然有效 → 仅重新"应用选中大小"(因为 createChart 后放大状态没保留)
-          if (this.chart) this.applySelectedSizeToChart(this.selectedKnowledgeId);
         }
       });
     },

+ 15 - 73
src/views/targetImprove/practice/knowledgeGraph/region.vue

@@ -30,9 +30,8 @@
           </div>
           <div class="row_right">
             <el-button size="small" class="add_btn weak_btn"
-              :disabled="isKnowledgeAdded(item)"
               @click="handleAddPractice(item)">
-              {{ isKnowledgeAdded(item) ? '已加入练习' : '加入练习' }}
+              加入练习
             </el-button>
           </div>
         </div>
@@ -69,9 +68,8 @@
           </div>
           <div class="row_right">
             <el-button size="small" class="add_btn good_btn"
-              :disabled="isKnowledgeAdded(item)"
               @click="handleAddPractice(item)">
-              {{ isKnowledgeAdded(item) ? '已加入练习' : '加入练习' }}
+              加入练习
             </el-button>
           </div>
         </div>
@@ -108,9 +106,8 @@
           </div>
           <div class="row_right">
             <el-button size="small" class="add_btn excellent_btn"
-              :disabled="isKnowledgeAdded(item)"
               @click="handleAddPractice(item)">
-              {{ isKnowledgeAdded(item) ? '已加入练习' : '加入练习' }}
+              加入练习
             </el-button>
           </div>
         </div>
@@ -147,9 +144,8 @@
           </div>
           <div class="row_right">
             <el-button size="small" class="add_btn advanced_btn"
-              :disabled="isKnowledgeAdded(item)"
               @click="handleAddPractice(item)">
-              {{ isKnowledgeAdded(item) ? '已加入练习' : '加入练习' }}
+              加入练习
             </el-button>
           </div>
         </div>
@@ -175,23 +171,12 @@ export default {
         consolidateZone: []
       })
     },
-    // 父级已成功"加入练习"的知识点ID映射:{ [knowledgeId]: true }
-    addedKnowledgeIds: {
-      type: Object,
-      default: () => ({})
-    },
     // 父级传入的"联动选中知识点ID"
     // - 环形扩散图选中的圆点,父级会把 knowledgeId 写到这里 → 本组件高亮对应行并滚动居中
     // - 点本组件任一行条目时,也通过 emit('item-click') 反向通知父级把 ID 同步到 circularDiffusion
     selectedKnowledgeId: {
       type: [String, Number],
       default: ''
-    },
-    // 图例可见状态联动:薄弱(1)/良好(2)/优秀(3) 的可见性
-    // 当某类图例被隐藏时,对应区域返回空数组,显示"暂无数据"
-    legendVisibility: {
-      type: Object,
-      default: () => ({ 1: true, 2: true, 3: true })
     }
   },
   data() {
@@ -240,22 +225,16 @@ export default {
   },
   computed: {
     // 薄弱补强区:直接用接口返回的 conquerZone
-    // 图例联动:当薄弱图例被隐藏时(legendVisibility[1]=false),返回空数组 → 显示"暂无数据"
     weakArea() {
-      const base = Array.isArray(this.partitionData?.conquerZone) ? this.partitionData.conquerZone : [];
-      return this.legendVisibility && this.legendVisibility[1] === false ? [] : base;
+      return Array.isArray(this.partitionData?.conquerZone) ? this.partitionData.conquerZone : [];
     },
     // 能力提升区:直接用接口返回的 improveZone
-    // 图例联动:当良好图例被隐藏时(legendVisibility[2]=false),返回空数组 → 显示"暂无数据"
     goodArea() {
-      const base = Array.isArray(this.partitionData?.improveZone) ? this.partitionData.improveZone : [];
-      return this.legendVisibility && this.legendVisibility[2] === false ? [] : base;
+      return Array.isArray(this.partitionData?.improveZone) ? this.partitionData.improveZone : [];
     },
     // 扎实巩固区:直接用接口返回的 consolidateZone
-    // 图例联动:当优秀图例被隐藏时(legendVisibility[3]=false),返回空数组 → 显示"暂无数据"
     excellentArea() {
-      const base = Array.isArray(this.partitionData?.consolidateZone) ? this.partitionData.consolidateZone : [];
-      return this.legendVisibility && this.legendVisibility[3] === false ? [] : base;
+      return Array.isArray(this.partitionData?.consolidateZone) ? this.partitionData.consolidateZone : [];
     },
     // 冲刺进阶区:暂时隐藏,预留空数组
     advancedArea() {
@@ -285,16 +264,6 @@ export default {
       const n = Number(fallback);
       return isNaN(n) ? 0 : n;
     },
-    /**
-     * 判断某个知识点条目是否"已加入练习"
-     * 判定:父级 prop addedKnowledgeIds 中有没有当前 knowledgeId 字段
-     */
-    isKnowledgeAdded(item) {
-      const id = item?.knowledgeId;
-      if (!id) return false;
-      return !!(this.addedKnowledgeIds
-        && Object.prototype.hasOwnProperty.call(this.addedKnowledgeIds, String(id)));
-    },
     /**
      * 判断某个知识点条目是不是"当前联动选中"(跨区单选:三/四个区只能有一条高亮)
      * 把父级 selectedKnowledgeId 和条目 knowledgeId 做字符串比较即可
@@ -519,7 +488,7 @@ export default {
   }
 }
 
-/* 行列表容器:仍然可滚动,但视觉上隐藏滚动条 */
+/* 行列表容器:可滚动,显示自定义滚动条 */
 .knowledge_rows {
   display: flex;
   flex-direction: column;
@@ -529,25 +498,22 @@ export default {
   min-height: 0;
   position: relative;
 
-  /* Webkit 系列(Chrome/Safari/新 Edge):彻底隐藏滚动条 */
   &::-webkit-scrollbar {
-    width: 0;
-    height: 0;
-    display: none;
+    width: 6px;
   }
 
   &::-webkit-scrollbar-thumb {
-    display: none;
+    background: #C0C4CC;
+    border-radius: 3px;
   }
 
   &::-webkit-scrollbar-track {
-    display: none;
+    background: #F5F7FA;
+    border-radius: 3px;
   }
 
-  /* Firefox:隐藏滚动条 */
-  scrollbar-width: none;
-  /* IE / 旧版 Edge:隐藏滚动条 */
-  -ms-overflow-style: none;
+  scrollbar-width: thin;
+  scrollbar-color: #C0C4CC #F5F7FA;
 }
 
 .empty_tip {
@@ -700,30 +666,6 @@ export default {
   background: #FFFFFF;
 }
 
-/* 【按钮置灰】加入练习成功后 :disabled 状态颜色统一为浅灰色
- *  必须写和 4 个主题色相同的高权重选择器链(.区 .knowledge_row .add_btn.区_btn),
- *  并叠加 .is-disabled / [disabled],否则会被上面的主题色(4 层 class 特异性)覆盖,表现为"按钮不能点但颜色没变灰"
- */
-.weak .knowledge_row .add_btn.weak_btn.is-disabled,
-.weak .knowledge_row .add_btn.weak_btn[disabled],
-.good .knowledge_row .add_btn.good_btn.is-disabled,
-.good .knowledge_row .add_btn.good_btn[disabled],
-.excellent .knowledge_row .add_btn.excellent_btn.is-disabled,
-.excellent .knowledge_row .add_btn.excellent_btn[disabled],
-.advanced .knowledge_row .add_btn.advanced_btn.is-disabled,
-.advanced .knowledge_row .add_btn.advanced_btn[disabled] {
-  border: 1px solid #DCDFE6 !important;
-  color: #C0C4CC !important;
-  background: #F5F7FA !important;
-  cursor: not-allowed !important;
-  &:hover,
-  &:focus {
-    border: 1px solid #DCDFE6 !important;
-    color: #C0C4CC !important;
-    background: #F5F7FA !important;
-  }
-}
-
 /* 响应式:小屏适配 */
 @media (max-width: 1366px) {
   .region_container {