| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703 |
- <template>
- <div class="knowledge_graph" ref="knowledgeGraph" :style="{ maxHeight: containerHeight + 'px' }">
- <div class="graph_top">
- <!-- 顶部信息模块 -->
- <div class="knowledge_header">
- <div class="header_top">
- <span class="header_title">我的知识图谱</span>
- <!-- 年级选择标签,只有一个年级时不显示 -->
- <el-checkbox-group v-if="gradeList && gradeList.length > 1" v-model="currentGradeList" @change="handleGradeChange">
- <el-checkbox v-for="grade in gradeList" :key="grade.value" :label="grade.value">
- {{ grade.label }}
- </el-checkbox>
- </el-checkbox-group>
- </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>
- </div>
- </div>
- <!-- 环形扩散图组件 -->
- <circular-diffusion :subjectName="actualSubjectName" :subjectScoreRate="subjectScoreRate"
- :tableData="displayTableData"
- :classLevel="classLevel" :classGroupName="classGroupName"
- :currentKnowledgeId="currentLinkedKnowledgeId || currentKnowledgeId || defaultKnowledgeId"
- :addedKnowledgeIds="addedKnowledgeIds"
- @legend-change="handleLegendChange" @activeTabChange="handleActiveTabChange"
- @add-practice="handleAddPractice"
- @knowledge-item-click="handleKnowledgeItemClick" />
- <!-- 区域展示组件 -->
- <region
- :partitionData="partitionData"
- :addedKnowledgeIds="addedKnowledgeIds"
- :selectedKnowledgeId="currentLinkedKnowledgeId || currentKnowledgeId || defaultKnowledgeId"
- @add-practice="handleAddPractice"
- @item-click="handleRegionItemClick" />
- </div>
- </div>
- </template>
- <script>
- import CircularDiffusion from './knowledgeGraph/circularDiffusion.vue';
- import Region from './knowledgeGraph/region.vue';
- import { mapState } from 'vuex';
- export default {
- name: 'KnowledgeGraph',
- components: {
- CircularDiffusion,
- Region
- },
- props: {
- 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,
- // 当前选中的年级列表(多选)
- currentGradeList: [],
- // 年级列表(从localStorage的student_gradesList读取)
- gradeList: [],
- // 中心圆点学科名称(通过 subjectCode 从 student_subjectList 匹配)
- subjectNameState: '数学知识点',
- // 默认选中知识点ID:接口返回的 knowList 中"第一棵节点树的最后一个子节点(叶子)"
- defaultKnowledgeId: '',
- // 接口返回的真实统计数据
- graphStats: {
- allKnowNum: 0, // 知识点总数
- weakKnowNum: 0, // 薄弱数量
- goodKnowNum: 0 // 待提升数量
- },
- // 接口返回的树形知识数据(第一层直接传给环形扩散图)
- knowList: [],
- // knowList 扁平化后的一维数组(用于统计、列表展示)
- flatKnowList: [],
- // 知识点分区:由 knowledge_partition 接口返回
- // conquerZone: 薄弱补强区 / improveZone: 能力提升区 / consolidateZone: 扎实巩固区
- partitionData: {
- conquerZone: [],
- improveZone: [],
- consolidateZone: []
- },
- // 当前会话中"已加入练习"的知识点 ID 容器(响应式:父级 $set 后自动驱动子组件按钮置灰)
- // 形如 { '1001': true }
- addedKnowledgeIds: {},
- // 环形扩散图 ↔ 区域卡片 的联动选中知识点 ID
- // - 点环形扩散图圆点 → 写这里 → region 读 prop :selectedKnowledgeId 高亮并滚动居中
- // - 点 region 某行条目 → 写这里 → circularDiffusion 读 prop :currentKnowledgeId 放大圆点 + 更新右侧折线表格
- currentLinkedKnowledgeId: '',
- // 假数据:表格树形结构数据(用于无接口数据时兜底)
- mockTableData: []
- };
- },
- computed: {
- ...mapState('target', ['subjectCode']),
- // 中心圆点学科名称:优先从 localStorage 的 student_subjectList 匹配 subjectCode
- actualSubjectName() {
- return this.subjectNameState || '知识点';
- },
- // 统计知识点总数:优先使用接口返回的 allKnowNum
- totalCount() {
- if (this.graphStats.allKnowNum !== null && this.graphStats.allKnowNum !== undefined) {
- return this.graphStats.allKnowNum;
- }
- return this.displayAllKnowledgeList.length;
- },
- // 薄弱知识点数量(knowledgeType === 1):优先使用接口返回的 weakKnowNum
- weakCount() {
- if (this.graphStats.weakKnowNum !== null && this.graphStats.weakKnowNum !== undefined) {
- return this.graphStats.weakKnowNum;
- }
- return this.displayAllKnowledgeList.filter(item => item.knowledgeType === 1).length;
- },
- // 良好/待提升知识点数量(knowledgeType === 2):优先使用接口返回的 goodKnowNum
- goodCount() {
- if (this.graphStats.goodKnowNum !== null && this.graphStats.goodKnowNum !== undefined) {
- return this.graphStats.goodKnowNum;
- }
- return this.displayAllKnowledgeList.filter(item => item.knowledgeType === 2).length;
- },
- // 优秀知识点数量(knowledgeType === 3)
- excellentCount() {
- return this.displayAllKnowledgeList.filter(item => item.knowledgeType === 3).length;
- },
- // 实际显示的表格数据:接口knowList > props.tableData > 假数据
- displayTableData() {
- if (this.knowList && this.knowList.length > 0) {
- return this.knowList;
- }
- if (this.tableData && this.tableData.length > 0) {
- return this.tableData;
- }
- return this.mockTableData;
- },
- // 实际显示的全部知识点数据:flatKnowList > props.allKnowledgeList > 假数据
- displayAllKnowledgeList() {
- if (this.flatKnowList && this.flatKnowList.length > 0) {
- return this.flatKnowList;
- }
- 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 }
- ];
- }
- },
- watch: {
- // 监听学科变化,重新调用知识图谱接口
- subjectCode(newVal, oldVal) {
- if (newVal && newVal !== oldVal) {
- this.fetchKnowledgeGraph();
- this.fetchKnowledgePartition();
- }
- }
- },
- mounted() {
- // 从localStorage读取年级列表
- this.loadGradeListFromStorage();
- // 初始化容器高度
- this.calculateContainerHeight();
- // 监听窗口大小变化
- window.addEventListener('resize', this.calculateContainerHeight);
- // 首次加载知识图谱数据 + 分区数据
- this.fetchKnowledgeGraph();
- this.fetchKnowledgePartition();
- },
- beforeDestroy() {
- // 移除窗口大小变化监听
- window.removeEventListener('resize', this.calculateContainerHeight);
- },
- methods: {
- /**
- * 从 localStorage 读取年级列表 student_gradesList
- * 并规范化为 [{ label, value }] 结构
- */
- loadGradeListFromStorage() {
- try {
- const raw = localStorage.getItem('student_gradesList');
- if (!raw) return;
- const list = JSON.parse(raw);
- if (!Array.isArray(list)) return;
- // 规范化字段:支持 label/value、label/code、name/value 等常见组合
- this.gradeList = list.map(item => ({
- label: item.label || item.name || String(item.value ?? item.code ?? item),
- value: item.value ?? item.code ?? item.label ?? item.name ?? String(item)
- }));
- // 默认全部选中
- this.currentGradeList = this.gradeList.map(g => g.value);
- } catch (e) {
- console.warn('[KnowledgeGraph] 解析 localStorage.student_gradesList 失败:', e);
- }
- },
- /**
- * 【接口调用】获取知识图谱数据
- * 参数:
- * - levelCode: 学段CODE(localStorage.student_level_code)
- * - gradeCodes: 年级codes数组(checkbox选中的value,即使只有1个也要传)
- * - subjectCode: 学科code(vuex -> target.subjectCode)
- * 调用成功后打印返回数据
- */
- fetchKnowledgeGraph() {
- // 读取学段CODE
- const levelCode = localStorage.getItem('student_level_code') || '';
- // 年级codes:当前选中的value数组;如未初始化则取gradeList全部value
- let gradeCodes = this.currentGradeList && this.currentGradeList.length
- ? this.currentGradeList
- : (this.gradeList || []).map(g => g.value);
- // 学科code
- const subjectCode = this.subjectCode || '';
- const params = {
- levelCode,
- gradeCodes,
- subjectCode
- };
- this.$api.target.knowledgeGraph(params).then(res => {
- if (res.code == 200 && res.data) {
- const d = res.data;
- // 1. 统计数据
- this.graphStats = {
- allKnowNum: d.allKnowNum !== undefined ? Number(d.allKnowNum) : 0,
- weakKnowNum: d.weakKnowNum !== undefined ? Number(d.weakKnowNum) : 0,
- goodKnowNum: d.goodKnowNum !== undefined ? Number(d.goodKnowNum) : 0
- };
- // 2. 中心圆点学科名称:通过 subjectCode 从 localStorage.student_subjectList 匹配
- this.resolveSubjectNameByCode();
- // 3. knowList 树形数据 + 拍平(并把 personalScoreRate 映射为 classScoreRate / gradeScoreRate 以便图表组件识别)
- const list = Array.isArray(d.knowList) ? d.knowList : [];
- this.knowList = this.mapKnowListItem(list);
- this.flatKnowList = this.flattenKnowList(this.knowList);
- // 4. 默认选中:第一棵节点树的最后一个子节点(叶子节点)
- const defaultNode = this.findFirstTreeLastLeafNode(this.knowList);
- this.defaultKnowledgeId = defaultNode ? (defaultNode.knowledgeId ?? '') : '';
- } else {
- // 接口失败 / code !== 200 / 无data:清空数据,扩散图和统计数字归零
- this.resetKnowledgeGraphEmpty();
- }
- }).catch(err => {
- console.error('[KnowledgeGraph] knowledgeGraph 接口异常:', err);
- this.resetKnowledgeGraphEmpty();
- });
- },
- /**
- * 参数和 knowledgeGraph 相同:levelCode / gradeCodes / subjectCode
- * 返回:conquerZone(薄弱) / improveZone(提升) / consolidateZone(巩固)
- */
- fetchKnowledgePartition() {
- const levelCode = localStorage.getItem('student_level_code') || '';
- let gradeCodes = this.currentGradeList && this.currentGradeList.length
- ? this.currentGradeList
- : (this.gradeList || []).map(g => g.value);
- const subjectCode = this.subjectCode || '';
- const params = { levelCode, gradeCodes, subjectCode };
- this.$api.target.knowledgePartition(params).then(res => {
- if (res.code == 200 && res.data) {
- const d = res.data || {};
- this.partitionData = {
- conquerZone: this.mapPartitionItem(Array.isArray(d.conquerZone) ? d.conquerZone : []),
- improveZone: this.mapPartitionItem(Array.isArray(d.improveZone) ? d.improveZone : []),
- consolidateZone:this.mapPartitionItem(Array.isArray(d.consolidateZone)? d.consolidateZone : [])
- };
- } else {
- this.resetPartitionEmpty();
- }
- }).catch(err => {
- console.error('[KnowledgeGraph] knowledgePartition 接口异常:', err);
- this.resetPartitionEmpty();
- });
- },
- /**
- * 分区接口返回的每个子数组字段规范化
- * - knowledgeId / knowledgeName / personalScoreRate / examNum / knowledgeType
- * 顺便兜底转换成 Number,避免字符串导致渲染异常
- */
- mapPartitionItem(list) {
- if (!Array.isArray(list)) return [];
- return list.map(item => {
- const node = { ...(item || {}) };
- node.knowledgeId = node.knowledgeId ?? node.id;
- node.knowledgeName = node.knowledgeName ?? node.name ?? node.label ?? '';
- node.knowledgeType = Number(node.knowledgeType ?? node.type ?? 3);
- const pRate = node.personalScoreRate ?? node.scoreRate ?? node.personalRate;
- if (pRate !== null && pRate !== undefined && pRate !== '') {
- const n = Number(pRate);
- node.personalScoreRate = isNaN(n) ? 0 : n;
- if (node.classScoreRate === null || node.classScoreRate === undefined) {
- node.classScoreRate = node.personalScoreRate;
- }
- if (node.gradeScoreRate === null || node.gradeScoreRate === undefined) {
- node.gradeScoreRate = node.personalScoreRate;
- }
- }
- // 考频:examNum
- if (node.examNum !== null && node.examNum !== undefined && node.examNum !== '') {
- const n = Number(node.examNum);
- node.examNum = isNaN(n) ? 0 : n;
- }
- return node;
- });
- },
- /**
- * 清空分区数据(接口失败或无数据时调用)
- */
- resetPartitionEmpty() {
- this.partitionData = {
- conquerZone: [],
- improveZone: [],
- consolidateZone: []
- };
- },
- /**
- * 清空知识图谱所有数据(接口失败或无数据时调用)
- * - knowList / flatKnowList 清空
- * - graphStats 三项归零
- * - defaultKnowledgeId 清空
- * - subjectNameState 不重置(保持本地匹配兜底名,避免名称闪烁)
- */
- resetKnowledgeGraphEmpty() {
- this.knowList = [];
- this.flatKnowList = [];
- this.graphStats = {
- allKnowNum: 0,
- weakKnowNum: 0,
- goodKnowNum: 0
- };
- this.defaultKnowledgeId = '';
- },
- /**
- * 从树形 knowList 中取"第一棵节点树的最后一个子节点(叶子)"
- * 规则:取 list[0] 作为根节点,然后一路沿 children[children.length-1] 下钻
- * 直到某节点没有 children 或 children 为空,返回该节点;
- * list 为空返回 null
- */
- findFirstTreeLastLeafNode(list) {
- if (!Array.isArray(list) || list.length === 0) return null;
- let node = list[0];
- while (node && Array.isArray(node.children) && node.children.length > 0) {
- node = node.children[node.children.length - 1];
- }
- return node || null;
- },
- /**
- * 通过 subjectCode 从 localStorage.student_subjectList 匹配学科名称
- * 并写入 this.subjectNameState
- */
- resolveSubjectNameByCode() {
- try {
- const subjectCode = String(this.subjectCode ?? '');
- if (!subjectCode) return;
- const raw = localStorage.getItem('student_subjectList');
- if (!raw) return;
- const list = JSON.parse(raw);
- if (!Array.isArray(list) || list.length === 0) return;
- const match = list.find(item => {
- const val = String(item.value ?? item.code ?? item.subjectCode ?? item.id ?? '');
- return val === subjectCode;
- });
- if (match) {
- const name = match.label ?? match.name ?? match.subjectName ?? String(match);
- if (name) {
- this.subjectNameState = name;
- return;
- }
- }
- // 兜底:取第一个
- const first = list[0];
- if (first) {
- const fallbackName = first.label ?? first.name ?? first.subjectName ?? String(first);
- if (fallbackName) this.subjectNameState = fallbackName;
- }
- } catch (e) {
- console.warn('[KnowledgeGraph] 解析 localStorage.student_subjectList 失败:', e);
- }
- },
- /**
- * 递归遍历 knowList,确保每个节点具备环形图需要的字段:
- * - knowledgeId / knowledgeName / knowledgeType
- * - personalScoreRate → 同时映射为 classScoreRate、gradeScoreRate(组件读取的是这两个)
- * - isJoin 原样保留(子组件需要据此显示"已加入练习")
- * - children 递归处理
- */
- mapKnowListItem(list) {
- if (!Array.isArray(list)) return [];
- return list.map(item => {
- const node = { ...(item || {}) };
- // 兼容字段
- node.knowledgeId = node.knowledgeId ?? node.id;
- node.knowledgeName = node.knowledgeName ?? node.name ?? node.label;
- node.knowledgeType = Number(node.knowledgeType ?? node.type ?? 3);
- const pRate = node.personalScoreRate ?? node.scoreRate ?? node.personalRate;
- if (pRate !== null && pRate !== undefined) {
- // 环形扩散图 generateTreeData 中会按 classLevel / mode 读取 classScoreRate 或 gradeScoreRate
- // 为了兼容两种路径,把 personalScoreRate 同时复制到两者
- if (node.classScoreRate === null || node.classScoreRate === undefined) {
- node.classScoreRate = Number(pRate);
- }
- if (node.gradeScoreRate === null || node.gradeScoreRate === undefined) {
- node.gradeScoreRate = Number(pRate);
- }
- node.personalScoreRate = Number(pRate);
- }
- if (node.children && node.children.length > 0) {
- node.children = this.mapKnowListItem(node.children);
- }
- return node;
- });
- },
- /**
- * 将 knowList(树形)拍平为一维数组(保留 isJoin 等全部字段)
- */
- flattenKnowList(list, out = []) {
- if (!Array.isArray(list)) return out;
- list.forEach(item => {
- out.push(item);
- if (item.children && item.children.length > 0) {
- this.flattenKnowList(item.children, out);
- }
- });
- return out;
- },
- // 年级切换(多选)
- handleGradeChange(gradeList) {
- this.currentGradeList = gradeList;
- this.$emit('grade-change', gradeList);
- // 年级变化后重新请求知识图谱 + 分区数据
- this.fetchKnowledgeGraph();
- this.fetchKnowledgePartition();
- },
- // 计算容器高度
- 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);
- }
- },
- // 处理图例变化
- handleLegendChange(data) {
- this.$emit('legend-change', data);
- },
- // 处理tab切换
- handleActiveTabChange(tab) {
- this.$emit('activeTabChange', tab);
- },
- // 处理知识点点击(来自环形扩散图组件:圆点被用户点击或默认选中)
- handleKnowledgeItemClick(data) {
- // 兼容两种 payload 结构:
- // 1) 圆点点击 / setDefaultSelection 新结构:{ knowledgeId, knowledgeName, ... } (顶层)
- // 2) 旧的 item 包装结构:{ item: {knowledgeId,...}, index: N }
- let knowledgeId = data?.knowledgeId;
- if ((knowledgeId === undefined || knowledgeId === null || knowledgeId === '')
- && data?.item) {
- knowledgeId = data.item.knowledgeId;
- }
- if (knowledgeId !== undefined && knowledgeId !== null && knowledgeId !== '') {
- this.currentLinkedKnowledgeId = String(knowledgeId);
- }
- this.$emit('knowledge-item-click', data);
- },
- // 处理区域卡片中某行条目被点击(来自 region 组件)→ 反向联动到环形扩散图对应圆点高亮 + 右侧数据
- handleRegionItemClick(item) {
- const id = item?.knowledgeId;
- if (id === undefined || id === null || id === '') return;
- this.currentLinkedKnowledgeId = String(id);
- },
- // 处理加入练习
- // 参数来源:
- // - 环形扩散图按钮:emit 传出 { knowledgeId, knowledgeName, personalScoreRate?, knowledgeType? }
- // - region 卡片按钮:emit 传出整条 item(含 knowledgeId/Name/personalScoreRate/knowledgeType/examNum...)
- handleAddPractice(item) {
- if (!item) {
- this.$message.warning('未获取到知识点信息');
- return;
- }
- const knowledgeId = item.knowledgeId ?? item.id;
- const knowledgeName = item.knowledgeName ?? item.name ?? item.label ?? '';
- if (!knowledgeId || !knowledgeName) {
- this.$message.warning('知识点信息不完整,请稍后重试');
- return;
- }
- // 已经加过的直接返回,避免重复请求
- if (this.addedKnowledgeIds[String(knowledgeId)]) {
- this.$message.info('该知识点已加入练习');
- return;
- }
- // 得分率:优先 personalScoreRate → gradeScoreRate → classScoreRate
- let personalScoreRate = item.personalScoreRate;
- if (personalScoreRate === null || personalScoreRate === undefined || personalScoreRate === '') {
- personalScoreRate = item.gradeScoreRate ?? item.classScoreRate;
- }
- if (personalScoreRate === null || personalScoreRate === undefined || personalScoreRate === '') {
- personalScoreRate = 0;
- } else {
- personalScoreRate = Number(personalScoreRate);
- if (isNaN(personalScoreRate)) personalScoreRate = 0;
- }
- // 知识点类型:1薄弱 2一般 3优秀
- let knowledgeType = Number(item.knowledgeType ?? item.type ?? 0);
- if (isNaN(knowledgeType)) knowledgeType = 0;
- const subjectCode = this.subjectCode || '';
- const levelCode = localStorage.getItem('student_level_code') || '';
- // 年级CODE:取 gradeList 数组中最后一个年级的 value
- // - 多个年级(如[{高一,10},{高二,11}])→ 取最后一个(高二.value = 11)
- // - 只有一个年级 → 直接取该年级 value
- // - 空/无效 → 传空串兜底
- let gradeCode = '';
- if (Array.isArray(this.gradeList) && this.gradeList.length > 0) {
- const lastGrade = this.gradeList[this.gradeList.length - 1];
- if (lastGrade && lastGrade.value !== undefined && lastGrade.value !== null && lastGrade.value !== '') {
- gradeCode = String(lastGrade.value);
- }
- }
- const params = {
- knowledgeId,
- knowledgeName,
- personalScoreRate,
- knowledgeType,
- subjectCode,
- levelCode,
- gradeCode
- };
- this.$api.target.addExercise(params).then(res => {
- if (res && (res.code == 200 || res.code == 0)) {
- // 成功:提示 + 记录 ID(用 $set 保证响应式,region/circularDiffusion 会自动重新渲染并置灰按钮)
- this.$message.success('已加入练习成功');
- this.$set(this.addedKnowledgeIds, String(knowledgeId), true);
- } else {
- const msg = (res && res.message) || '加入练习失败';
- this.$message.error(msg);
- }
- }).catch(err => {
- console.error('[KnowledgeGraph] addExercise 接口异常:', err);
- this.$message.error('加入练习失败,请稍后重试');
- });
- }
- }
- };
- </script>
- <style lang="scss" scoped>
- .knowledge_graph {
- overflow-y: auto;
- overflow-x: hidden;
- .graph_top {
- background: #FFFFFF;
- border-radius: 10px;
- padding: 20px;
- }
- /* 顶部信息模块 */
- .knowledge_header {
- margin-bottom: 10px;
- .header_top {
- display: flex;
- align-items: center;
- gap: 20px;
- margin-bottom: 10px;
- .header_title {
- font-size: 16px;
- font-weight: 500;
- color: #333;
- }
- .el-checkbox-group{
- .el-checkbox{
- margin-right:20px;
- }
- }
- }
- .header_stats {
- font-size: 14px;
- .stats_text {
- color: #999999;
- }
- .stats_num {
- color: #2E64FA;
- }
- .stats_weak {
- color: #F56C6C;
- }
- .stats_good {
- color: #FAC858;
- }
- }
- }
- /* 自定义滚动条样式 */
- &::-webkit-scrollbar {
- width: 6px;
- }
- &::-webkit-scrollbar-track {
- background: #f1f1f1;
- border-radius: 3px;
- }
- &::-webkit-scrollbar-thumb {
- background: #c1c1c1;
- border-radius: 3px;
- &:hover {
- background: #a8a8a8;
- }
- }
- }
- </style>
|