|
|
@@ -1,7 +1,7 @@
|
|
|
<template>
|
|
|
<div class="knowledge_graph">
|
|
|
<!-- 顶部切换按钮 -->
|
|
|
- <div class="graph_header" v-if="mode === 'student' && activeView === 'graph'">
|
|
|
+ <div class="graph_header" >
|
|
|
<!-- 学生模式:显示对比选择器和视图切换按钮 -->
|
|
|
<div class="comparison_selector">
|
|
|
<el-button-group>
|
|
|
@@ -10,9 +10,6 @@
|
|
|
{{ option.label }}
|
|
|
</el-button>
|
|
|
</el-button-group>
|
|
|
- <!-- <div class="student_position">
|
|
|
- 朱睿涵位置:{{ studentPosition }}
|
|
|
- </div> -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
@@ -112,37 +109,36 @@
|
|
|
<span>暂无数据</span>
|
|
|
</div>
|
|
|
<!-- 正常列表数据 -->
|
|
|
+
|
|
|
<div class="list_item" v-for="(item, index) in knowledgeItems" :key="index"
|
|
|
@click="handleItemClick(item, index)" :class="{ active: selectedIndex === index }" v-else>
|
|
|
-
|
|
|
<div class="item_header">
|
|
|
<span class="item_dot" :style="{ backgroundColor: getDotColor(item) }"></span>
|
|
|
<el-tooltip :content="item.knowledgeName" placement="top" effect="light"
|
|
|
:disabled="!item.knowledgeName || item.knowledgeName.length < 15">
|
|
|
<span class="item_title">{{ item.knowledgeName }}</span>
|
|
|
</el-tooltip>
|
|
|
- <span class="item_tag" v-if="item.scoreRateDiff">{{ item.scoreRateDiff }} %</span>
|
|
|
+ <span class="item_tag"
|
|
|
+ v-if="item.scoreRateDiff" :style="{ backgroundColor: parseFloat(item.scoreRateDiff) > 0 ? '#3BA272' : '#F56C6C' }">
|
|
|
+ {{ item.scoreRateDiff }}%
|
|
|
+ </span>
|
|
|
</div>
|
|
|
|
|
|
<div class="item_info">
|
|
|
- <!-- 班级/学生得分率 -->
|
|
|
- <!-- <span class="item_score">
|
|
|
- <span class="score_label">得分率:</span>
|
|
|
- <span class="score_first" v-if="item.classScoreRate">{{ item.classScoreRate }}%</span>
|
|
|
- <span class="score_separator" v-if="item.classScoreRate">|</span>
|
|
|
- <span v-if="item.gradeScoreRate"
|
|
|
- :class="item.classScoreRate !== null ? 'score_second' : 'score_first'">{{ item.gradeScoreRate }}%</span>
|
|
|
- </span> -->
|
|
|
-
|
|
|
<!-- 班级/学生得分率 -->
|
|
|
<span class="item_score">
|
|
|
- <span class="score_label">得分率:</span>
|
|
|
+ <span class="score_label" v-if="item.personalScoreRate">个人得分率:</span>
|
|
|
<span :style="{color: getDotColor(item)}" v-if="item.personalScoreRate">{{ item.personalScoreRate }}%</span>
|
|
|
- <span class="score_separator" v-if="item.personalScoreRate">|</span>
|
|
|
- <span v-if="item.personalScoreRate" class="score_second">{{ item.classScoreRate }}%</span>
|
|
|
- <span v-if="!item.personalScoreRate" :style="{color: getDotColor(item)}">{{ item.classScoreRate }}%</span>
|
|
|
+ <span class="score_separator" v-if="item.classScoreRate">|</span>
|
|
|
+ <span class="score_label">班级得分率:</span>
|
|
|
+ <span v-if="item.classScoreRate" class="score_second">{{ item.classScoreRate }}%</span>
|
|
|
+ <span v-if="!item.classScoreRate" :style="{color: getDotColor(item)}">{{ item.classScoreRate }}%</span>
|
|
|
</span>
|
|
|
|
|
|
+ <!-- 班级/学生得分率 -->
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
<span class="item_exam_count">考试数: <span class="exam_count">{{ item.examNum || 0 }}</span></span>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -216,7 +212,7 @@
|
|
|
<span class="rate_dot" :class="getRateClass(row.scoreRateDiff)"></span>
|
|
|
<span class="diff_value"
|
|
|
:class="{ 'diff_negative': row.scoreRateDiff !== null && row.scoreRateDiff < 0 }">
|
|
|
- {{ row.scoreRateDiff !== null && row.scoreRateDiff >= 0 ? '+' : '' }}{{ row.scoreRateDiff }}%
|
|
|
+ {{ row.scoreRateDiff !== null && row.scoreRateDiff > 0 ? '+' : '' }}{{ row.scoreRateDiff }}%
|
|
|
</span>
|
|
|
</div>
|
|
|
</template>
|
|
|
@@ -233,13 +229,6 @@ import * as echarts from 'echarts';
|
|
|
export default {
|
|
|
name: 'KnowledgeGraph',
|
|
|
props: {
|
|
|
- mode: {
|
|
|
- type: String,
|
|
|
- default: 'class',
|
|
|
- validator: (value) => {
|
|
|
- return ['class', 'student'].includes(value);
|
|
|
- }
|
|
|
- },
|
|
|
activeView: { // 当前视图,graph或list 年级画像还是学生画像
|
|
|
type: String,
|
|
|
default: 'graph',
|
|
|
@@ -259,10 +248,6 @@ export default {
|
|
|
type: Array,
|
|
|
default: () => []
|
|
|
},
|
|
|
- classGroupName: { // 班级名称
|
|
|
- type: String,
|
|
|
- default: ''
|
|
|
- },
|
|
|
fatalVulnerability: { // 零分知识点数据
|
|
|
type: Array,
|
|
|
default: () => []
|
|
|
@@ -274,7 +259,19 @@ export default {
|
|
|
allKnowledgeList: { // 全部知识点数据
|
|
|
type: Array,
|
|
|
default: () => []
|
|
|
- }
|
|
|
+ },
|
|
|
+ classLevel: { // 判断是否是组合还是班级,还是年级
|
|
|
+ type: [String, Number],
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ classGroupName: { // 班级组合名称
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ currentKnowledgeId: { // 当前选中的知识点ID
|
|
|
+ type: [String, Number],
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
},
|
|
|
computed: {
|
|
|
// 根据当前tab返回对应的数据
|
|
|
@@ -294,6 +291,8 @@ export default {
|
|
|
chart: null, // echarts实例
|
|
|
// 组件挂载状态标记
|
|
|
_isMounted: false,
|
|
|
+ // 首次渲染标志,用于控制初始化时的透明度逻辑
|
|
|
+ _isFirstRender: true,
|
|
|
// Tab切换状态
|
|
|
activeTab: 'all',
|
|
|
// 对比选择器数据
|
|
|
@@ -316,7 +315,11 @@ export default {
|
|
|
// 选中的知识点索引
|
|
|
selectedIndex: 0,
|
|
|
// 当前选中的行数据
|
|
|
- selectedRow: null
|
|
|
+ selectedRow: null,
|
|
|
+ // 当前选中的知识点ID,用于控制节点透明度
|
|
|
+ selectedKnowledgeId: '',
|
|
|
+ // 树形表格展开行keys
|
|
|
+ expandRowKeys: []
|
|
|
};
|
|
|
},
|
|
|
mounted() {
|
|
|
@@ -339,8 +342,23 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
watch: {
|
|
|
+ // 监听classLevel变化,当对比选择器数据变化时更新对比选项
|
|
|
+ classLevel: {
|
|
|
+ handler(newVal, oldVal) {
|
|
|
+
|
|
|
+ },
|
|
|
+ immediate: true
|
|
|
+ },
|
|
|
+ // 监听classGroupName变化,当班级组合名称变化时更新对比选项
|
|
|
+ classGroupName: {
|
|
|
+ handler(newVal, oldVal) {
|
|
|
+ if (newVal) {
|
|
|
+ }
|
|
|
+ },
|
|
|
+ immediate: true
|
|
|
+ },
|
|
|
// 监听activeView变化,当切换到图形视图时重新初始化图表,切换到列表视图时销毁图表
|
|
|
- activeView(newView) {
|
|
|
+ activeView(newView, oldView) {
|
|
|
if (newView === 'graph') {
|
|
|
this.$nextTick(() => {
|
|
|
this.initChart();
|
|
|
@@ -351,17 +369,35 @@ export default {
|
|
|
this.chart.dispose();
|
|
|
this.chart = null;
|
|
|
}
|
|
|
+ // 切换到列表视图时,展开所有树形节点
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.expandAllNodes();
|
|
|
+ });
|
|
|
}
|
|
|
},
|
|
|
// 监听tab切换,更新选中索引并通知父组件
|
|
|
activeTab(newTab, oldTab) {
|
|
|
this.selectedIndex = 0;
|
|
|
+
|
|
|
+ // 根据当前tab获取对应的数据列表
|
|
|
+ const currentList = this.knowledgeItems;
|
|
|
+ // 如果有数据,更新selectedKnowledgeId为第一条数据的ID
|
|
|
+ if (currentList && currentList.length > 0) {
|
|
|
+ this.selectedKnowledgeId = currentList[0].knowledgeId;
|
|
|
+ // 如果当前是图形视图且图表已初始化,更新图表选中状态
|
|
|
+ if (this.activeView === 'graph' && this.chart) {
|
|
|
+ this.updateChart();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 如果没有数据,清空selectedKnowledgeId
|
|
|
+ this.selectedKnowledgeId = '';
|
|
|
+ }
|
|
|
|
|
|
// 只有当组件已挂载且tab值实际发生变化,并且不是初始化时,才向父组件发送事件
|
|
|
// 这是为了避免在视图切换时重复调用接口
|
|
|
if (this._isMounted && newTab !== oldTab) {
|
|
|
// 向父组件发送tab切换事件
|
|
|
- this.$emit('activeTabChange', newTab);
|
|
|
+ this.$emit('active-tab-change', newTab);
|
|
|
}
|
|
|
},
|
|
|
// 监听数据变化,重新设置默认选中项
|
|
|
@@ -391,6 +427,34 @@ export default {
|
|
|
this.setDefaultSelection();
|
|
|
}
|
|
|
}
|
|
|
+ },
|
|
|
+ // 监听tableData变化,更新展开行
|
|
|
+ tableData: {
|
|
|
+ deep: true,
|
|
|
+ handler() {
|
|
|
+ // 当tableData变化时,重新展开所有节点
|
|
|
+ if (this.activeView === 'list') {
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.expandAllNodes();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ if (this.chart) {
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.updateChart();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 监听当前选中的知识点ID变化,更新图表高亮状态
|
|
|
+ currentKnowledgeId: {
|
|
|
+ handler(newVal) {
|
|
|
+ // 更新selectedKnowledgeId,确保图表使用正确的高亮状态
|
|
|
+ this.selectedKnowledgeId = newVal;
|
|
|
+ // 仅当当前视图是图形视图且图表已初始化时才更新
|
|
|
+ if (this.activeView === 'graph' && this.chart) {
|
|
|
+ this.updateChart();
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
},
|
|
|
methods: {
|
|
|
@@ -398,27 +462,41 @@ export default {
|
|
|
setDefaultSelection() {
|
|
|
// 保存当前tab值,用于比较是否需要修改
|
|
|
const oldActiveTab = this.activeTab;
|
|
|
+ let selectedItem = null;
|
|
|
|
|
|
- // 优先级:全部知识点 > 高频错题知识点 > 零分知识点
|
|
|
+ // 优先级:全部知识点> 高频错题知识点 > 零分知识点
|
|
|
if (this.allKnowledgeList && this.allKnowledgeList.length > 0) {
|
|
|
// 如果前两者都没有数据但全部知识点有数据,切换到全部知识点并选中第一条
|
|
|
if (this.activeTab !== 'all') {
|
|
|
this.activeTab = 'all';
|
|
|
}
|
|
|
this.selectedIndex = 0;
|
|
|
- }else if (this.highVulnerability && this.highVulnerability.length > 0) {
|
|
|
- // 如果零分知识点没有数据但高频错题知识点有数据,切换到高频错题知识点并选中第一条
|
|
|
+ selectedItem = this.allKnowledgeList[0];
|
|
|
+ } else if (this.highVulnerability && this.highVulnerability.length > 0) {
|
|
|
+ // 如果全部知识点没有数据但高频错题知识点有数据,切换到高频错题知识点并选中第一条
|
|
|
if (this.activeTab !== 'highFreq') {
|
|
|
this.activeTab = 'highFreq';
|
|
|
}
|
|
|
this.selectedIndex = 0;
|
|
|
- }else if (this.fatalVulnerability && this.fatalVulnerability.length > 0) {
|
|
|
+ selectedItem = this.highVulnerability[0];
|
|
|
+ } else if (this.fatalVulnerability && this.fatalVulnerability.length > 0) {
|
|
|
// 如果零分知识点有数据,切换到零分知识点并选中第一条
|
|
|
if (this.activeTab !== 'zero') {
|
|
|
this.activeTab = 'zero';
|
|
|
}
|
|
|
this.selectedIndex = 0;
|
|
|
- }
|
|
|
+ selectedItem = this.fatalVulnerability[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果有选中项,更新selectedKnowledgeId,实现初始化默认选择
|
|
|
+ if (selectedItem) {
|
|
|
+ this.selectedKnowledgeId = selectedItem.knowledgeId;
|
|
|
+ // 如果当前是图形视图且图表已初始化,更新图表选中状态
|
|
|
+ if (this.activeView === 'graph' && this.chart) {
|
|
|
+ this.updateChart();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
// 只有当tab值实际发生变化时,才触发选中事件
|
|
|
// 初始化时不触发,避免重复调用接口
|
|
|
if (this._isMounted && oldActiveTab !== this.activeTab) {
|
|
|
@@ -426,12 +504,77 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
|
|
|
+ // 递归收集所有节点的key,用于展开所有树形节点
|
|
|
+ collectAllRowKeys() {
|
|
|
+ const keys = [];
|
|
|
+
|
|
|
+ // 递归函数,收集所有有children的节点key
|
|
|
+ const collectKeys = (data) => {
|
|
|
+ if (!data || !Array.isArray(data)) return;
|
|
|
+
|
|
|
+ data.forEach(item => {
|
|
|
+ // 如果节点有children,将其key添加到keys数组中
|
|
|
+ if (item.children && item.children.length > 0) {
|
|
|
+ // 使用knowledgeId作为key
|
|
|
+ keys.push(item.knowledgeId);
|
|
|
+ // 递归处理children
|
|
|
+ collectKeys(item.children);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 调用递归函数
|
|
|
+ collectKeys(this.tableData);
|
|
|
+
|
|
|
+ // 更新expandRowKeys数组
|
|
|
+ this.expandRowKeys = keys;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 使用vxe-table API展开所有树形节点
|
|
|
+ expandAllNodes() {
|
|
|
+ // 获取vxe-table实例
|
|
|
+ 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.tableData);
|
|
|
+
|
|
|
+ // 使用vxe-table 3.7.5兼容的API展开所有节点
|
|
|
+ if (treeTable.setExpandRows) {
|
|
|
+ // 如果支持setExpandRows方法
|
|
|
+ treeTable.setExpandRows(expandNodes);
|
|
|
+ } else if (treeTable.setTreeExpand) {
|
|
|
+ // 如果支持setTreeExpand方法
|
|
|
+ expandNodes.forEach(node => {
|
|
|
+ treeTable.setTreeExpand(node, true);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
// 切换查看方式
|
|
|
switchView(view) {
|
|
|
// 通过emit事件通知父组件更新activeView
|
|
|
this.$emit('view-change', view);
|
|
|
},
|
|
|
|
|
|
+ // 年级、班级组合判断
|
|
|
+ classIsGroup() {
|
|
|
+ if (this.classLevel == 1) {
|
|
|
+ return true;
|
|
|
+ }else
|
|
|
+ return false;
|
|
|
+ },
|
|
|
+
|
|
|
// 根据得分率获取对应的颜色
|
|
|
getNodeColor(scoreRate) {
|
|
|
const rate = parseFloat(scoreRate);
|
|
|
@@ -516,31 +659,51 @@ export default {
|
|
|
};
|
|
|
|
|
|
// 基于表格数据生成树形图数据
|
|
|
- const generateTreeData = (data, level = 0) => {
|
|
|
+ // 添加一个可选参数overrideKnowledgeId,用于临时覆盖当前的currentKnowledgeId
|
|
|
+ const generateTreeData = (data, level = 0, overrideKnowledgeId = null) => {
|
|
|
+ // 使用传入的overrideKnowledgeId或默认使用vm.selectedKnowledgeId
|
|
|
+ const currentKnowledgeId = overrideKnowledgeId !== null ? overrideKnowledgeId : vm.selectedKnowledgeId;
|
|
|
+
|
|
|
return data.filter(item => {
|
|
|
// 获取节点级别
|
|
|
- const nodeLevel = getNodeLevel(item.gradeScoreRate);
|
|
|
+ const nodeLevel = vm.classLevel === 0 ? getNodeLevel(item.gradeScoreRate) : getNodeLevel(item.classScoreRate);
|
|
|
// 根据图例选中状态决定是否显示节点
|
|
|
return vm.selectedLegend[nodeLevel];
|
|
|
}).map(item => {
|
|
|
- // 根据节点是否为最外围节点设置大小
|
|
|
- // 最外围节点(没有子节点的叶子节点)直径为32像素,其他节点直径为18像素
|
|
|
+ // 根据节点是否为最外围节点
|
|
|
const isOutermost = !item.children || item.children.length === 0;
|
|
|
- const symbolSize = isOutermost ? 16 : 10;
|
|
|
-
|
|
|
+
|
|
|
+ // 检查当前节点是否匹配选中的知识点ID
|
|
|
+ const isMatched = isOutermost && String(item.knowledgeId) === String(currentKnowledgeId);
|
|
|
+
|
|
|
+ // 设置节点大小:选中状态16px,未选中状态10px,非叶子节点5px
|
|
|
+ let symbolSize = 5;
|
|
|
+ if (isOutermost) {
|
|
|
+ symbolSize = isMatched ? 15 : 10;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算节点样式
|
|
|
+ const itemColor = isOutermost ?
|
|
|
+ vm.getNodeColor(vm.classLevel === 0 || vm.classLevel === 1 ? item.gradeScoreRate : item.classScoreRate) :
|
|
|
+ '#BFC1C7';
|
|
|
+
|
|
|
const node = {
|
|
|
name: item.knowledgeName,
|
|
|
symbolSize: symbolSize,
|
|
|
// 根据班级名称是否为年级来选择使用gradeScoreRate或classScoreRate
|
|
|
- value: vm.classGroupName === '年级' ? item.gradeScoreRate : item.classScoreRate,
|
|
|
+ value: vm.classLevel === 0 || vm.classLevel === 1 ? item.gradeScoreRate : item.classScoreRate,
|
|
|
itemStyle: {
|
|
|
- // 根据班级名称是否为年级来选择使用gradeScoreRate或classScoreRate
|
|
|
- color: vm.getNodeColor(vm.classGroupName === '年级' ? item.gradeScoreRate : item.classScoreRate)
|
|
|
- }
|
|
|
+ color: itemColor,
|
|
|
+ opacity: 1
|
|
|
+ },
|
|
|
+ // 添加isOutermost属性用于tooltip判断
|
|
|
+ isOutermost: isOutermost,
|
|
|
+ // 保存知识ID用于匹配
|
|
|
+ knowledgeId: item.knowledgeId
|
|
|
};
|
|
|
|
|
|
if (item.children && item.children.length > 0) {
|
|
|
- node.children = generateTreeData(item.children, level + 1);
|
|
|
+ node.children = generateTreeData(item.children, level + 1, overrideKnowledgeId);
|
|
|
}
|
|
|
|
|
|
return node;
|
|
|
@@ -551,12 +714,23 @@ export default {
|
|
|
const option = {
|
|
|
tooltip: {
|
|
|
formatter: (params) => {
|
|
|
- // 只有根节点(treePathInfo存在且长度为1)使用组件的subjectScoreRate作为得分率
|
|
|
+ // 检查是否为根节点
|
|
|
if (params.treePathInfo && params.treePathInfo.length === 1) {
|
|
|
- return `${params.name}<br/>得分率: ${this.subjectScoreRate}%`;
|
|
|
+ // 根节点也属于非最外层节点,只显示名称
|
|
|
+ return params.name;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查节点是否为最外层节点(叶子节点)
|
|
|
+ // 使用isOutermost属性或检查data.isOutermost
|
|
|
+ const isOutermost = params.data && params.data.isOutermost;
|
|
|
+
|
|
|
+ if (isOutermost) {
|
|
|
+ // 最外层节点显示完整信息(名称+得分率)
|
|
|
+ return `${params.name}<br/>得分率: ${params.value !== null && params.value !== undefined ? params.value : '0'}%`;
|
|
|
+ } else {
|
|
|
+ // 非最外层节点只显示名称
|
|
|
+ return params.name;
|
|
|
}
|
|
|
- // 其他节点(包括treePathInfo不存在的情况)都使用value作为得分率
|
|
|
- return `${params.name}<br/>得分率: ${params.value !== null && params.value !== undefined ? params.value : '0'}%`;
|
|
|
}
|
|
|
},
|
|
|
animationDurationUpdate: 1500,
|
|
|
@@ -573,7 +747,7 @@ export default {
|
|
|
roam: true,
|
|
|
|
|
|
// 使用基础配置实现居中
|
|
|
- center: ['10%', '8%'],
|
|
|
+ center: ['10%', '7%'],
|
|
|
|
|
|
zoom: this.getZoomValue(),
|
|
|
scaleLimit: {
|
|
|
@@ -590,14 +764,14 @@ export default {
|
|
|
data: [
|
|
|
{
|
|
|
name: this.subjectName,
|
|
|
- symbolSize: 10,
|
|
|
+ symbolSize: 5,
|
|
|
value: this.subjectScoreRate, // 添加value属性,用于显示得分率
|
|
|
itemStyle: {
|
|
|
- // 根据得分率动态设置颜色
|
|
|
- color: this.subjectScoreRate >= 85 ? '#3BA272' : // 优秀 - 绿色
|
|
|
- this.subjectScoreRate >= 60 ? '#FAC858' : // 良好 - 黄色
|
|
|
- '#EE6666' // 薄弱 - 红色
|
|
|
+ // 根节点为非最外层节点,使用#EBEEF5颜色
|
|
|
+ color: '#BFC1C7'
|
|
|
},
|
|
|
+ // 标记为非最外层节点
|
|
|
+ isOutermost: false,
|
|
|
children: generateTreeData(this.tableData)
|
|
|
}
|
|
|
],
|
|
|
@@ -606,13 +780,13 @@ export default {
|
|
|
},
|
|
|
lineStyle: {
|
|
|
color: '#ECEEF3',
|
|
|
- width: 2,
|
|
|
+ width: 1,
|
|
|
type: 'solid'
|
|
|
},
|
|
|
emphasis: {
|
|
|
focus: 'adjacency',
|
|
|
lineStyle: {
|
|
|
- width: 2
|
|
|
+ width: 1
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -624,6 +798,195 @@ export default {
|
|
|
|
|
|
// 初始化后调用resize确保图表正确显示
|
|
|
this.chart.resize();
|
|
|
+
|
|
|
+ // 添加图表点击事件监听
|
|
|
+ this.chart.on('click', (params) => {
|
|
|
+ // 明确识别点击类型
|
|
|
+ const isOuterNodeClick = params.data && params.data.isOutermost;
|
|
|
+ const isEmptyAreaClick = !params.data || !params.componentType || params.componentType === '';
|
|
|
+ const isNonOuterNodeClick = params.data && !params.data.isOutermost;
|
|
|
+
|
|
|
+ if (isOuterNodeClick) {
|
|
|
+ // 处理最外层节点点击
|
|
|
+ // 确定当前显示的列表数据
|
|
|
+ let targetList = [];
|
|
|
+ if (this.activeTab === 'zero') {
|
|
|
+ targetList = this.fatalVulnerability;
|
|
|
+ } else if (this.activeTab === 'highFreq') {
|
|
|
+ targetList = this.highVulnerability;
|
|
|
+ } else {
|
|
|
+ targetList = this.allKnowledgeList;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 1. 首先尝试直接从当前列表中根据名称查找
|
|
|
+ let knowledgeItem = targetList.find(item => item.knowledgeName === params.name);
|
|
|
+ let targetIndex = -1;
|
|
|
+
|
|
|
+ if (knowledgeItem) {
|
|
|
+ targetIndex = targetList.findIndex(item => item.knowledgeId === knowledgeItem.knowledgeId);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 如果在当前列表中找不到,尝试从其他列表中查找
|
|
|
+ if (!knowledgeItem) {
|
|
|
+ const allLists = [this.allKnowledgeList, this.highVulnerability, this.fatalVulnerability];
|
|
|
+ for (const list of allLists) {
|
|
|
+ knowledgeItem = list.find(item => item.knowledgeName === params.name);
|
|
|
+ if (knowledgeItem) {
|
|
|
+ // 找到后切换到对应tab
|
|
|
+ if (list === this.highVulnerability) {
|
|
|
+ this.activeTab = 'highFreq';
|
|
|
+ targetList = this.highVulnerability;
|
|
|
+ } else if (list === this.fatalVulnerability) {
|
|
|
+ this.activeTab = 'zero';
|
|
|
+ targetList = this.fatalVulnerability;
|
|
|
+ } else {
|
|
|
+ this.activeTab = 'all';
|
|
|
+ targetList = this.allKnowledgeList;
|
|
|
+ }
|
|
|
+ targetIndex = targetList.findIndex(item => item.knowledgeId === knowledgeItem.knowledgeId);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 如果还是找不到,尝试从tableData中查找
|
|
|
+ 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) {
|
|
|
+ // 在所有列表中查找匹配的knowledgeId
|
|
|
+ const allLists = [this.allKnowledgeList, this.highVulnerability, this.fatalVulnerability];
|
|
|
+ for (const list of allLists) {
|
|
|
+ targetIndex = list.findIndex(item => {
|
|
|
+ // 考虑类型转换,确保比较准确
|
|
|
+ return String(item.knowledgeId) === String(knowledgeItem.knowledgeId);
|
|
|
+ });
|
|
|
+ if (targetIndex !== -1) {
|
|
|
+ // 切换到对应tab
|
|
|
+ if (list === this.highVulnerability) {
|
|
|
+ this.activeTab = 'highFreq';
|
|
|
+ targetList = this.highVulnerability;
|
|
|
+ } else if (list === this.fatalVulnerability) {
|
|
|
+ this.activeTab = 'zero';
|
|
|
+ targetList = this.fatalVulnerability;
|
|
|
+ } else {
|
|
|
+ this.activeTab = 'all';
|
|
|
+ targetList = this.allKnowledgeList;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (knowledgeItem && targetIndex !== -1) {
|
|
|
+ // 向父组件发送事件
|
|
|
+ this.$emit('knowledge-item-click', {
|
|
|
+ item: knowledgeItem,
|
|
|
+ index: targetIndex
|
|
|
+ });
|
|
|
+
|
|
|
+ // 更新选中索引和选中知识点ID
|
|
|
+ this.selectedIndex = targetIndex;
|
|
|
+ this.selectedKnowledgeId = knowledgeItem.knowledgeId;
|
|
|
+
|
|
|
+ // 将首次渲染标志设为false,后续点击操作将应用透明度降低逻辑
|
|
|
+ // this._isFirstRender = false;
|
|
|
+
|
|
|
+ // 重新生成图表数据,确保只有当前选中节点高亮
|
|
|
+ this.chart.setOption({
|
|
|
+ series: [{
|
|
|
+ data: [
|
|
|
+ {
|
|
|
+ name: this.subjectName,
|
|
|
+ symbolSize: 5,
|
|
|
+ value: this.subjectScoreRate,
|
|
|
+ itemStyle: {
|
|
|
+ color: '#BFC1C7',
|
|
|
+ opacity: 1
|
|
|
+ },
|
|
|
+ isOutermost: false,
|
|
|
+ children: generateTreeData(this.tableData, 0, this.selectedKnowledgeId)
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }]
|
|
|
+ }, {
|
|
|
+ animation: true,
|
|
|
+ animationDuration: 300
|
|
|
+ });
|
|
|
+
|
|
|
+ // 滚动到选中项
|
|
|
+ this.$nextTick(() => {
|
|
|
+ const listContainer = this.$el.querySelector('.knowledge_list');
|
|
|
+ const listItems = listContainer.querySelectorAll('.list_item');
|
|
|
+ if (listItems[targetIndex]) {
|
|
|
+ listContainer.scrollTop = listItems[targetIndex].offsetTop - 100;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // 打印详细调试信息
|
|
|
+ console.log('点击处理调试信息:', {
|
|
|
+ paramsName: params.name,
|
|
|
+ params: params,
|
|
|
+ knowledgeItem: knowledgeItem,
|
|
|
+ targetIndex: targetIndex,
|
|
|
+ activeTab: this.activeTab,
|
|
|
+ targetListLength: targetList.length,
|
|
|
+ targetListSample: targetList.slice(0, 3),
|
|
|
+ tableDataSample: this.tableData.slice(0, 1)
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (isEmptyAreaClick || isNonOuterNodeClick) {
|
|
|
+ // 点击空白区域或非最外层节点,恢复所有节点透明度
|
|
|
+ // 清空selectedKnowledgeId,确保所有节点都高亮
|
|
|
+ this.selectedKnowledgeId = '';
|
|
|
+
|
|
|
+ // 调用generateTreeData时传入overrideKnowledgeId为空字符串,确保所有节点都不透明
|
|
|
+ const updatedOption = {
|
|
|
+ series: [{
|
|
|
+ data: [
|
|
|
+ {
|
|
|
+ name: this.subjectName,
|
|
|
+ symbolSize: 5,
|
|
|
+ value: this.subjectScoreRate,
|
|
|
+ itemStyle: {
|
|
|
+ color: '#BFC1C7',
|
|
|
+ opacity: 1
|
|
|
+ },
|
|
|
+ isOutermost: false,
|
|
|
+ // 传入overrideKnowledgeId为空字符串,确保生成的所有节点都不透明
|
|
|
+ children: generateTreeData(this.tableData, 0, '')
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }]
|
|
|
+ };
|
|
|
+ // 更新图表,使用动画实现平滑过渡
|
|
|
+ this.chart.setOption(updatedOption, {
|
|
|
+ animation: true,
|
|
|
+ animationDuration: 300
|
|
|
+ });
|
|
|
+
|
|
|
+ // 向父组件发送事件,清除选中的知识点ID
|
|
|
+ this.$emit('knowledge-item-click', {
|
|
|
+ item: null,
|
|
|
+ index: -1
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
|
|
|
// 监听窗口大小变化
|
|
|
window.addEventListener('resize', () => {
|
|
|
@@ -688,8 +1051,15 @@ export default {
|
|
|
handleItemClick(item, index) {
|
|
|
// 更新选中索引
|
|
|
this.selectedIndex = index;
|
|
|
+ // 更新选中知识点ID,实现反向选择功能
|
|
|
+ this.selectedKnowledgeId = item.knowledgeId;
|
|
|
// 向父组件发送事件,传递点击的知识点数据
|
|
|
this.$emit('knowledge-item-click', { item, index });
|
|
|
+
|
|
|
+ // 如果当前是图形视图且图表已初始化,更新图表选中状态
|
|
|
+ if (this.activeView === 'graph' && this.chart) {
|
|
|
+ this.updateChart();
|
|
|
+ }
|
|
|
},
|
|
|
|
|
|
// 处理树形表格行点击事件
|
|
|
@@ -836,13 +1206,6 @@ export default {
|
|
|
justify-content: flex-end;
|
|
|
font-size: 14px;
|
|
|
|
|
|
- // 学生模式(学生提升)时,绝对定位在右上角
|
|
|
- &.student_mode {
|
|
|
- // position: absolute;
|
|
|
- // top: 0;
|
|
|
- // right: 25px;
|
|
|
- }
|
|
|
-
|
|
|
.switch_btn {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
@@ -1135,7 +1498,7 @@ export default {
|
|
|
border-radius: 8px;
|
|
|
|
|
|
span {
|
|
|
- margin-top: 40%;
|
|
|
+ margin-top: 23%;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -1182,7 +1545,7 @@ export default {
|
|
|
}
|
|
|
|
|
|
.item_tag {
|
|
|
- background: #F56C6C;
|
|
|
+
|
|
|
color: #FFFFFF;
|
|
|
font-size: 12px;
|
|
|
padding: 4px 6px;
|
|
|
@@ -1236,7 +1599,7 @@ export default {
|
|
|
// vxe表格样式
|
|
|
.list_content {
|
|
|
border-radius: 10px 10px 10px 10px;
|
|
|
- max-height: 480px;
|
|
|
+
|
|
|
|
|
|
.vxe-table {
|
|
|
|
|
|
@@ -1258,6 +1621,9 @@ export default {
|
|
|
border-radius: 8px;
|
|
|
overflow-y: auto;
|
|
|
overflow-x: hidden;
|
|
|
+
|
|
|
+ // 使用CSS变量设置全局行高
|
|
|
+ --vxe-ui-table-row-height-default: 20px !important;
|
|
|
|
|
|
// 表头样式
|
|
|
.vxe-table--header {
|
|
|
@@ -1274,27 +1640,50 @@ export default {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 行样式
|
|
|
- .vxe-body--row {
|
|
|
- border-bottom: 1px solid #F0F2F5;
|
|
|
- cursor: pointer !important;
|
|
|
- height: 52px !important;
|
|
|
-
|
|
|
- &:hover {
|
|
|
- background-color: #f5f7fa !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;
|
|
|
- }
|
|
|
+ &.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;
|
|
|
+ &.selected-row {
|
|
|
+ background-color: rgba(195, 219, 255, 0.2) !important;
|
|
|
+ color: #2E64FA !important;
|
|
|
+ cursor: pointer !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ // 单元格样式,设置内边距为0,确保行高由行高属性控制
|
|
|
+ .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 {
|