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