Explorar el Código

图谱接口添加参数

吴朋磊 hace 1 mes
padre
commit
a76c21e2da

+ 10 - 5
src/views/layout/components/Header.vue

@@ -201,20 +201,25 @@ export default {
     this.activeMenu();
 
     //学生端默认 个人中心 和分析报告两个菜单
-    this.menuList = [
+    const baseMenuList = [
       {
         menuName: "分析报告",
         externalLink: "/jointStudentAnalysisReport/list",
       },
-      // {
-      //   menuName: "靶向提升",
-      //   externalLink: "/targetImprove",
-      // },
       {
         menuName: "个人中心",
         externalLink: "/userInfo",
       },
     ];
+    // 当访问域名不是 www.k12100.com 时,才显示"靶向提升"菜单
+    const isK12Domain = window.location.hostname === 'www.k12100.com';
+    if (!isK12Domain) {
+      baseMenuList.splice(1, 0, {
+        menuName: "靶向提升",
+        externalLink: "/targetImprove",
+      });
+    }
+    this.menuList = baseMenuList;
     
   },
   methods: {

+ 1 - 1
src/views/targetImprove/practice/knowledgeGraph.vue

@@ -737,7 +737,7 @@ export default {
 
   /* 顶部信息模块 */
   .knowledge_header {
-    margin-bottom: 10px;
+    margin-bottom: 20px;
 
     .header_top {
       display: flex;

+ 440 - 11
src/views/targetImprove/practice/knowledgeGraph/circularDiffusion.vue

@@ -3,7 +3,7 @@
     <!-- 教师端样式 -->
     <div class="knowledge_class">
       <!-- 图形查看容器 -->
-      <div  class="graph_content">
+      <div  class="graph_content" v-if="activeView === 'graph'">
         <!-- 左侧图表 -->
         <div class="chart_container">
           <!-- 顶部容器:图例和视图切换按钮 -->
@@ -12,22 +12,33 @@
             <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>
+                <span class="legend_text">{{ '薄弱(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>
+                  {{'良好(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>
+                    <span class="legend_text">{{'优秀(85%≤得分率)'}}</span>
               </div>
             </div>
+
+            <!-- 视图切换按钮 -->
+            <div class="view_switcher_container" v-if="activeView === 'graph'" :class="activeView">
+              <el-button type="graph" @click="switchView('graph')" class="switch_btn graph_btn">
+                <i class="icon_switch_graph">
+                  <img src="../../../../assets/studentAnalysis/circleBlue.svg" alt="按图形查看">
+                </i>
+                按图形查看
+              </el-button>
+              <el-button type="text" class="switch_btn list_btn" @click="switchView('list')">
+                <i class="icon_switch_list"><img src="../../../../assets/studentAnalysis/iconGrey.svg" alt="按列表查看"></i>
+                按列表查看
+              </el-button>
+            </div>
           </div>
           <div ref="chart" class="chart" style="width: 100%;"></div>
         </div>
@@ -78,6 +89,80 @@
           </div>
         </div>
       </div>
+
+      <!-- 列表视图头部(图例 + 视图切换按钮) -->
+      <div class="graph_header" v-if="activeView === 'list'">
+        <!-- 自定义图例 -->
+        <div class="legend">
+          <div class="legend_item" :class="{ 'selected': selectedLegend.weak }" @click="toggleLegend('weak')">
+            <span class="legend_dot weak" :style="selectedLegend.weak ? {} : { backgroundColor: '#ccc' }"></span>
+            <span class="legend_text" :style="selectedLegend.weak ? {} : { color: '#999999' }">{{ '薄弱(0%≤得分率<60%)'}}</span>
+          </div>
+          <div class="legend_item" :class="{ 'selected': selectedLegend.good }" @click="toggleLegend('good')">
+            <span class="legend_dot good" :style="selectedLegend.good ? {} : { backgroundColor: '#ccc' }"></span>
+            <span class="legend_text" :style="selectedLegend.good ? {} : { color: '#999999' }">{{ '良好(60%≤得分率<85%)'}}</span>
+          </div>
+          <div class="legend_item" :class="{ 'selected': selectedLegend.excellent }" @click="toggleLegend('excellent')">
+            <span class="legend_dot excellent"
+              :style="selectedLegend.excellent ? {} : { backgroundColor: '#ccc' }"></span>
+            <span class="legend_text" :style="selectedLegend.excellent ? {} : { color: '#999999' }">{{ '优秀(85%≤得分率)'}}</span>
+          </div>
+        </div>
+        <!-- 视图切换按钮 -->
+        <div class="view_switcher_container">
+          <el-button type="text" @click="switchView('graph')" class="switch_btn list_btn">
+            <i class="icon_switch_graph">
+              <img src="../../../../assets/studentAnalysis/circleGrey.svg" alt="按图形查看">
+            </i>
+            按图形查看
+          </el-button>
+          <el-button type="graph" class="switch_btn graph_btn" @click="switchView('list')">
+            <i class="icon_switch_list">
+              <img src="../../../../assets/studentAnalysis/iconBlue.svg" alt="按列表查看">
+            </i>
+            按列表查看
+          </el-button>
+        </div>
+      </div>
+
+      <!-- 列表查看容器 -->
+      <div v-if="activeView === 'list'" class="list_content">
+        <vxe-table ref="treeTable" :data="filteredTableDataForList" style="width: 100%;" maxHeight="480px" border show-header
+          :tree-config="{ childrenField: 'children', hasChildField: 'hasChildren', showIcon: true, iconOpen: 'el-icon-remove', iconClose: 'el-icon-circle-plus' }"
+          :scroll-y="{ enabled: true, gt: 480 }" fixed-header :header-cell-style="{ color: '#333' }"
+          @row-click="handleRowClick" @cell-click="handleCellClick" :row-class-name="rowClassName">
+
+          <vxe-table-column type="tree" tree-node prop="title" :title="subjectName">
+            <template #default="{ row }">
+              <div class="knowledge_item">
+                <span class="knowledge_title">{{ row.knowledgeName }}</span>
+              </div>
+            </template>
+          </vxe-table-column>
+
+          <!-- 个人得分率 -->
+          <vxe-table-column prop="personalScoreRate" title="个人得分率" width="180" align="center">
+            <template #default="{ row }">
+              <div class="rate_info" v-if="row.personalScoreRate !== null">
+                <span class="rate_dot" :class="getRateClass(row.personalScoreRate)"></span>
+                <span class="rate_value">{{ row.personalScoreRate }}%</span>
+              </div>
+            </template>
+          </vxe-table-column>
+          <!-- 个人知识点状态 -->
+          <vxe-table-column prop="personalScoreRate" title="个人知识点状态" width="120" align="center">
+            <template #default="{ row }">
+              <div class="status_info" v-if="row.personalScoreRate !== null">
+                <span class="status_value"
+                  :style="{ color: getRateName(row.personalScoreRate) === '优秀' ? '#3BA272' : getRateName(row.personalScoreRate) === '良好' ? '#FAC858' : '#EE6666' }">
+                  {{ getRateName(row.personalScoreRate) }}
+                </span>
+              </div>
+            </template>
+          </vxe-table-column>
+        </vxe-table>
+      </div>
+
     </div>
   </div>
 </template>
@@ -268,6 +353,49 @@ export default {
         return list.filter(row => row.dataType === 1 || row.type === 'exam');
       }
       return list.filter(row => row.dataType === 2 || row.type === 'practice');
+    },
+    // 列表视图:根据图例选中状态过滤树形表格数据
+    filteredTableDataForList() {
+      if (!Array.isArray(this.tableData)) return [];
+      // 递归过滤:保留父节点(即使自身类型被隐藏,只要子树有可见节点就保留)
+      const filterNodes = (nodes) => {
+        if (!Array.isArray(nodes)) return [];
+        return nodes.map(item => {
+          const node = { ...item };
+          const rate = parseFloat(node.personalScoreRate);
+          let level = 'weak';
+          if (!isNaN(rate)) {
+            if (rate >= 85) level = 'excellent';
+            else if (rate >= 60) level = 'good';
+            else level = 'weak';
+          }
+          // 递归处理子节点
+          if (Array.isArray(node.children) && node.children.length > 0) {
+            node.children = filterNodes(node.children);
+          }
+          // 标记是否应该隐藏
+          node._legendHidden = !this.selectedLegend[level];
+          return node;
+        });
+      };
+      const filtered = filterNodes(this.tableData);
+      // 第二遍:移除"自身被隐藏且没有可见子节点"的节点
+      const removeHidden = (nodes) => {
+        if (!Array.isArray(nodes)) return [];
+        const result = [];
+        for (const node of nodes) {
+          const hasVisibleChildren = node.children && node.children.length > 0;
+          if (node._legendHidden && !hasVisibleChildren) {
+            continue; // 跳过:自身隐藏且无可见子节点
+          }
+          if (node.children) {
+            node.children = removeHidden(node.children);
+          }
+          result.push(node);
+        }
+        return result;
+      };
+      return removeHidden(filtered);
     }
   },
   data() {
@@ -276,6 +404,7 @@ export default {
       lineChart: null,
       _isMounted: false,
       _isFirstRender: true,
+      activeView: 'graph',
       activeComparison: null,
       selectedLegend: {
         weak: true,
@@ -288,7 +417,9 @@ export default {
       selectedKnowledgeType: 1,
       chartRefreshKey: 0,
       showKnowledgePrompt: this.isRecordKnowledge,
-      activeTableTab: 'all'
+      activeTableTab: 'all',
+      selectedRow: null,
+      expandRowKeys: []
     };
   },
   mounted() {
@@ -308,6 +439,27 @@ export default {
     }
   },
   watch: {
+    // 监听activeView变化,切换到列表视图时展开所有树形节点,切换到图形视图时初始化图表
+    activeView(newView, oldView) {
+      if (newView === 'list') {
+        this.$nextTick(() => {
+          this.expandAllNodes();
+        });
+      } else if (newView === 'graph') {
+        this.$nextTick(() => {
+          this.initChart();
+          this.initLineChart();
+        });
+      }
+    },
+    // 图例过滤后数据变化,重新展开所有树形节点
+    filteredTableDataForList() {
+      if (this.activeView === 'list') {
+        this.$nextTick(() => {
+          this.expandAllNodes();
+        });
+      }
+    },
     // 当知识点列表数据变化时,更新选中项的类型与折线图/表格
     allKnowledgeList: {
       handler(newVal) {
@@ -655,8 +807,73 @@ export default {
       });
     },
     switchView(view) {
+      this.activeView = view;
       this.$emit('view-change', view);
     },
+    // 根据得分率获取对应的样式类
+    getRateClass(rate) {
+      const rateValue = parseFloat(rate);
+      if (rateValue >= 85) {
+        return 'rate_excellent';
+      } else if (rateValue >= 60) {
+        return 'rate_good';
+      } else {
+        return 'rate_weak';
+      }
+    },
+    // 根据得分率获取对应的状态名称
+    getRateName(rate) {
+      const rateValue = parseFloat(rate);
+      if (rateValue >= 85) {
+        return '优秀';
+      } else if (rateValue >= 60) {
+        return '良好';
+      } else {
+        return '薄弱';
+      }
+    },
+    // 行样式类名方法
+    rowClassName({ row }) {
+      if (this.selectedRow && this.selectedRow.knowledgeId === row.knowledgeId) {
+        return 'selected-row';
+      }
+      return row.highlight ? 'row_highlight' : '';
+    },
+    // 处理树形表格行点击事件
+    handleRowClick(row) {
+      const isLastLevel = !row.children || row.children.length === 0;
+      if (isLastLevel) {
+        this.selectedRow = row;
+      }
+    },
+    // 处理单元格点击事件
+    handleCellClick({ row }) {
+      this.handleRowClick(row);
+    },
+    // 展开所有树形节点
+    expandAllNodes() {
+      const treeTable = this.$refs.treeTable;
+      if (treeTable) {
+        const expandNodes = [];
+        const collectExpandNodes = (data) => {
+          if (!data || !Array.isArray(data)) return;
+          data.forEach(item => {
+            if (item.children && item.children.length > 0) {
+              expandNodes.push(item);
+              collectExpandNodes(item.children);
+            }
+          });
+        };
+        collectExpandNodes(this.filteredTableDataForList);
+        if (treeTable.setExpandRows) {
+          treeTable.setExpandRows(expandNodes);
+        } else if (treeTable.setTreeExpand) {
+          expandNodes.forEach(node => {
+            treeTable.setTreeExpand(node, true);
+          });
+        }
+      }
+    },
     classIsGroup() {
       if (this.classLevel == 1) {
         return true;
@@ -1202,7 +1419,7 @@ export default {
     }
 
     .graph_header {
-      margin-bottom: 20px;
+      margin-bottom: 10px;
       position: relative;
       display: flex;
       justify-content: space-between;
@@ -1281,7 +1498,7 @@ export default {
       min-width: 0;
 
       .knowledge_right_container {
-        width: 500px;
+        width: 400px;
         max-width: 100%;
         display: flex;
         flex-direction: column;
@@ -1458,7 +1675,7 @@ export default {
           }
 
           .data_table {
-            height: 330px;
+            height: 360px;
             border-radius: 6px;
             /* 整表统一 cell 高度为 40px(Element UI 是通过 .cell 控制) */
             :deep(.el-table__header-wrapper th .cell),
@@ -1630,6 +1847,218 @@ export default {
         }
       }
     }
+
+    // 视图切换按钮样式
+    .view_switcher_container {
+      display: flex;
+      gap: 20px;
+      z-index: 10;
+      justify-content: flex-end;
+      font-size: 14px;
+
+      .switch_btn {
+        display: flex;
+        align-items: center;
+        padding: 0;
+        background-color: transparent;
+        border: none;
+        border-radius: 0;
+        font-size: 14px;
+        transition: color 0.3s ease;
+        cursor: pointer;
+        font-weight: 400;
+        color: #999999;
+
+        &:hover {
+          color: #2E64FA;
+        }
+      }
+
+      .graph_btn {
+        color: #2E64FA;
+      }
+
+      // 图标样式,确保与文字对齐
+      .icon_switch_graph,
+      .icon_switch_list {
+        img {
+          display: inline-block;
+          vertical-align: middle;
+          margin: 0;
+          padding: 0;
+          margin-top: -3px;
+        }
+      }
+
+      // 鼠标悬停时图片换色效果
+      .switch_btn:hover .icon_switch_graph img,
+      .switch_btn:hover .icon_switch_list img {
+        filter: brightness(0) saturate(100%) invert(34%) sepia(100%) saturate(5000%) hue-rotate(210deg) brightness(95%) contrast(100%);
+      }
+    }
+
+    // vxe表格样式
+    .list_content {
+      border-radius: 10px 10px 10px 10px;
+
+      .vxe-table {
+        // 展开收起的样式
+        :deep(.el-icon-circle-plus) {
+          color: #DCDFE6 !important;
+          font-size: 16px !important;
+        }
+
+        :deep(.el-icon-remove) {
+          color: #2E64FA !important;
+          font-size: 16px !important;
+        }
+      }
+
+      /* 设置表格行高度和光标样式 */
+      :deep {
+        .vxe-table {
+          border-radius: 8px;
+          overflow-y: auto;
+          overflow-x: hidden;
+
+          --vxe-ui-table-row-height-default: 20px !important;
+
+          // 表头样式
+          .vxe-table--header {
+            display: table-header-group !important;
+            visibility: visible !important;
+            opacity: 1 !important;
+            height: 52px !important;
+          }
+
+          .vxe-table--header {
+            .vxe-header--row {
+              height: 52px !important;
+            }
+          }
+
+          // 行样式
+          .vxe-table--body {
+            tbody {
+              tr {
+                border-bottom: 1px solid #F0F2F5;
+                cursor: pointer !important;
+                height: 20px !important;
+                line-height: 20px !important;
+                min-height: 20px !important;
+                max-height: 20px !important;
+
+                &:hover {
+                  background-color: #f5f7fa !important;
+                }
+
+                &.row_highlight {
+                  background-color: rgba(195, 219, 255, 0.2);
+                  cursor: pointer !important;
+                }
+
+                &.selected-row {
+                  background-color: rgba(195, 219, 255, 0.2) !important;
+                  color: #2E64FA !important;
+                  cursor: pointer !important;
+                }
+              }
+            }
+          }
+
+          // 单元格样式
+          .vxe-body--column,
+          .vxe-header--column {
+            padding: 0 !important;
+            height: 20px !important;
+            line-height: 20px !important;
+            min-height: 20px !important;
+            max-height: 20px !important;
+          }
+
+          .vxe-table--body {
+            min-height: 0 !important;
+          }
+
+          /* 自定义树形图标样式 */
+          .vxe-tree-icon {
+            display: inline-flex;
+            align-items: center;
+            justify-content: center;
+            width: 14px;
+            height: 14px;
+            border-radius: 50%;
+            margin-right: 6px;
+            font-size: 12px;
+            font-weight: bold;
+            line-height: 1;
+          }
+
+          .vxe-tree-icon--minus {
+            background-color: #409EFF;
+            color: white;
+          }
+
+          .vxe-tree-icon--plus {
+            background-color: #C0C4CC;
+            color: white;
+          }
+
+          // 斑马纹
+          .vxe-table--body {
+            tr:nth-child(even) {
+              background-color: #F5F7FA;
+            }
+          }
+        }
+      }
+
+      .knowledge_item {
+        display: flex;
+        align-items: center;
+
+        .knowledge_title {
+          font-size: 14px;
+          color: #606266;
+        }
+      }
+
+      .rate_info {
+        align-items: center;
+        gap: 8px;
+        justify-content: center;
+        width: 80px;
+        margin: 0 auto;
+        position: relative;
+      }
+
+      .rate_dot {
+        display: inline-block;
+        width: 6px;
+        height: 6px;
+        border-radius: 50%;
+        position: absolute;
+        top: 8px;
+        left: 2px;
+
+        &.rate_excellent {
+          background-color: #3BA272;
+        }
+
+        &.rate_good {
+          background-color: #FAC858;
+        }
+
+        &.rate_weak {
+          background-color: #EE6666;
+        }
+      }
+
+      .rate_value {
+        font-size: 14px;
+        color: #606266;
+      }
+    }
   }
 }
 </style>