| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906 |
- <template>
- <div class="knowledge_graph">
- <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>
- <!-- 视图切换按钮 - 移到knowledge_graph容器下 -->
- <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 === '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" :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" 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>
- <!-- 右侧容器,包含tab和知识点列表 -->
- <div class="knowledge_right_container">
- <!-- Tab切换 -->
- <div class="knowledge_tab">
- <div class="tab_item" :class="{ active: activeTab === 'all' }" @click="activeTab = 'all'">
- 全部知识点
- </div>
- <div class="tab_item" :class="{ active: activeTab === 'highFreq' }" @click="activeTab = 'highFreq'">
- 高频错题知识点
- </div>
- <div class="tab_item" :class="{ active: activeTab === 'zero' }" @click="activeTab = 'zero'">
- 零分知识点
- </div>
- </div>
- <!-- 右侧知识点列表 -->
- <div class="knowledge_list">
- <!-- 暂无数据提示 -->
- <div class="module_chart no_content_data no_data" style="height: 240px; " element-loading-background="#ffffff"
- v-if="!knowledgeItems || knowledgeItems.length === 0">
- <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, 'personalScoreRate') }"></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"
- :style="{ backgroundColor: parseFloat(item.scoreRateDiff) > 0 ? '#3BA272' : '#F56C6C' }">
- <i>{{ item.scoreRateDiff }}% </i>
- <i v-if="parseFloat(item.scoreRateDiff) !== 0" style="margin-top: 0px">{{ parseFloat(item.scoreRateDiff) > 0 ? '↑' : '↓' }}</i>
- </span> -->
- <span class="item_tag" v-if="item.isPush == 1" style="background-color: #2E64FA;">
- 有推题
- </span>
- </div>
- <div class="item_info">
- <!-- 班级/学生得分率 -->
- <span class="item_score">
- <span class="score_label" v-if="item.personalScoreRate">个人得分率:</span>
- <span :style="{ color: getDotColor(item, 'personalScoreRate') }" v-if="item.personalScoreRate">{{
- item.personalScoreRate }}%</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, 'classScoreRate') }">{{
- item.classScoreRate }}%</span> -->
- </span>
- <!-- 班级/学生得分率 -->
- <span class="item_exam_count">考试数: <span class="exam_count">{{ item.examNum || 0 }}</span></span>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 列表查看容器 -->
- <div v-if="activeView === 'list'" class="list_content">
- <vxe-table ref="treeTable" :data="tableData" 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-column prop="classScoreRate" title="班级得分率" width="180" align="center">
- <template #default="{ row }">
- <div class="rate_info" v-if="row.classScoreRate !== null">
- <span class="rate_dot" :class="getRateClass(row.classScoreRate)"></span>
- <span class="rate_value">{{ row.classScoreRate }}%</span>
- </div>
- </template>
- </vxe-table-column> -->
- <!-- 班级知识点状态 -->
- <!-- <vxe-table-column prop="classScoreRate" title="班级知识点状态" width="120" align="center">
- <template #default="{ row }">
- <div class="status_info" v-if="row.classScoreRate !== null">
- <span class="status_value"
- :style="{ color: getRateName(row.classScoreRate) === '优秀' ? '#3BA272' : getRateName(row.classScoreRate) === '良好' ? '#FAC858' : '#EE6666' }">
- {{ getRateName(row.classScoreRate) }}
- </span>
- </div>
- </template>
- </vxe-table-column> -->
- <!-- 只有当班级名称不是年级且不为空时,才显示得分率差列 -->
- <!-- <vxe-table-column prop="diff" title="得分率差" width="150" align="center">
- <template #default="{ row }">
- <div class="rate_info" v-if="row.scoreRateDiff !== null">
- <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 }}%
- </span>
- </div>
- </template>
- </vxe-table-column> -->
- </vxe-table>
- </div>
- </div>
- </template>
- <script>
- // 引入echarts
- import * as echarts from 'echarts';
- export default {
- name: 'KnowledgeGraph',
- props: {
- activeView: { // 当前视图,graph或list 年级画像还是学生画像
- 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: () => []
- },
- classLevel: { // 判断是否是组合还是班级,还是年级
- type: [String, Number],
- default: ''
- },
- classGroupName: { // 班级组合名称
- type: String,
- default: ''
- },
- currentKnowledgeId: { // 当前选中的知识点ID
- type: [String, Number],
- default: ''
- },
- },
- computed: {
- // 根据当前tab返回对应的数据
- knowledgeItems() {
- if (this.activeTab === 'zero') {
- return this.fatalVulnerability || [];
- } else if (this.activeTab === 'highFreq') {
- return this.highVulnerability || [];
- } else if (this.activeTab === 'all') {
- return this.allKnowledgeList || [];
- }
- return [];
- }
- },
- data() {
- return {
- chart: null, // echarts实例
- // 组件挂载状态标记
- _isMounted: false,
- // 首次渲染标志,用于控制初始化时的透明度逻辑
- _isFirstRender: true,
- // Tab切换状态
- activeTab: 'all',
- // 图例选中状态
- selectedLegend: {
- weak: true,
- good: true,
- excellent: true
- },
- // 选中的知识点索引
- selectedIndex: 0,
- // 当前选中的行数据
- selectedRow: null,
- // 当前选中的知识点ID,用于控制节点透明度
- selectedKnowledgeId: '',
- // 树形表格展开行keys
- expandRowKeys: []
- };
- },
- mounted() {
- // 设置组件挂载状态
- this._isMounted = true;
- // 无论在什么模式下,只要默认视图是图形视图就初始化echarts图表
- if (this.activeView === 'graph') {
- this.$nextTick(() => {
- this.initChart();
- });
- }
- },
- beforeDestroy() {
- // 设置组件未挂载状态
- this._isMounted = false;
- // 销毁echarts实例
- if (this.chart) {
- this.chart.dispose();
- }
- },
- watch: {
- // 监听classLevel变化,当对比选择器数据变化时更新对比选项
- classLevel: {
- handler(newVal, oldVal) {
- },
- immediate: true
- },
- // 监听classGroupName变化,当班级组合名称变化时更新对比选项
- classGroupName: {
- handler(newVal, oldVal) {
- if (newVal) {
- }
- },
- immediate: true
- },
- // 监听activeView变化,当切换到图形视图时重新初始化图表,切换到列表视图时销毁图表
- activeView(newView, oldView) {
- if (newView === 'graph') {
- this.$nextTick(() => {
- this.initChart();
- });
- } else if (newView === 'list') {
- // 切换到列表视图时销毁图表实例
- if (this.chart) {
- 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);
- }
- },
- // 监听数据变化,重新设置默认选中项
- fatalVulnerability: {
- deep: true,
- handler() {
- // 仅当当前视图是图形视图且数据发生变化时才更新默认选中
- if (this.activeView === 'graph') {
- this.setDefaultSelection();
- }
- }
- },
- highVulnerability: {
- deep: true,
- handler() {
- // 仅当当前视图是图形视图且数据发生变化时才更新默认选中
- if (this.activeView === 'graph') {
- this.setDefaultSelection();
- }
- }
- },
- allKnowledgeList: {
- deep: true,
- handler() {
- // 仅当当前视图是图形视图且数据发生变化时才更新默认选中
- if (this.activeView === 'graph') {
- 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: {
- // 设置默认选中项
- 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;
- selectedItem = this.allKnowledgeList[0];
- } else if (this.highVulnerability && this.highVulnerability.length > 0) {
- // 如果全部知识点没有数据但高频错题知识点有数据,切换到高频错题知识点并选中第一条
- if (this.activeTab !== 'highFreq') {
- this.activeTab = 'highFreq';
- }
- this.selectedIndex = 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) {
- // 组件已挂载且tab值发生变化时才发送事件
- }
- },
- // 递归收集所有节点的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;
- },
- // 切换图例选中状态
- toggleLegend(type) {
- this.selectedLegend[type] = !this.selectedLegend[type];
- // 生成scoreRateTypes数组
- let scoreRateTypes = [];
- if (this.selectedLegend.weak) scoreRateTypes.push(1);
- if (this.selectedLegend.good) scoreRateTypes.push(2);
- if (this.selectedLegend.excellent) scoreRateTypes.push(3);
- // 如果包含所有值,则传null,否则传数组
- if (scoreRateTypes.length === 3) {
- scoreRateTypes = null;
- }
- // 向父组件发送图例变化事件,包含scoreRateTypes和当前activeTab对应的knowledgeType
- let knowledgeType = 0;
- if (this.activeTab === 'highFreq') {
- knowledgeType = 1;
- } else if (this.activeTab === 'zero') {
- knowledgeType = 2;
- } else {
- knowledgeType = 0;
- }
- this.$emit('legend-change', { scoreRateTypes, knowledgeType });
- },
- // 根据得分率获取对应的颜色
- getNodeColor(scoreRate) {
- const rate = parseFloat(scoreRate);
- if (rate >= 85) {
- return '#3BA272'; // 优秀 - 绿色
- } else if (rate >= 60) {
- return '#FAC858'; // 良好 - 黄色
- } else {
- return '#EE6666'; // 薄弱 - 红色
- }
- },
- // 更新图表缩放和布局参数,防止窗口变化时图表变形
- updateChartLayout() {
- if (this.chart) {
- // 重新计算最外层知识点数量
- 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 = () => {
- const screenWidth = window.innerWidth;
- let zoom = 1.05;
-
- if (screenWidth < 1100) {
- zoom = 1.05;
- } else if (screenWidth >= 1100 && screenWidth <= 1200) {
- zoom = 1.05;
- } else {
- zoom = 1.05;
- }
-
- if (outermostCount <= 5) {
- zoom *= 1.0;
- }
-
- return zoom;
- };
-
- // 重新计算内外半径
- const getOptimalRadius = () => {
- if (outermostCount <= 5) {
- return ['20%', '65%'];
- }
- return ['12%', '75%'];
- };
-
- // 更新图表的所有动态参数
- this.chart.setOption({
- series: [{
- // 更新缩放比例
- zoom: getOptimalZoom(),
- // 更新内外半径
- radius: getOptimalRadius(),
- // 更新层级间距
- layerPadding: outermostCount <= 5 ? [20, 10] : [10, 5]
- }]
- });
- }
- },
- // 初始化echarts图表
- initChart() {
- try {
- // 检查chart容器是否存在
- if (!this.$refs.chart) {
- console.error('图表容器不存在');
- return;
- }
- this.createChart();
- }
- catch (error) {
- console.error('图表初始化失败:', error);
- }
- },
- // 创建图表
- createChart() {
- try {
- // 检查chart容器是否存在
- if (!this.$refs.chart) {
- console.error('图表容器不存在');
- return;
- }
- // 销毁现有的图表实例
- if (this.chart) {
- this.chart.dispose();
- }
- // 创建echarts实例
- this.chart = echarts.init(this.$refs.chart);
- // 保存当前组件实例的引用
- const vm = this;
- // 根据得分率获取对应的级别
- const getNodeLevel = (scoreRate) => {
- const rate = parseFloat(scoreRate);
- // 处理scoreRate为null、undefined或非数字的情况
- if (isNaN(rate)) {
- return 'weak'; // 默认为薄弱
- }
- if (rate >= 85) {
- return 'excellent'; // 优秀
- } else if (rate >= 60) {
- return 'good'; // 良好
- } else {
- return 'weak'; // 薄弱
- }
- };
- // 基于表格数据生成树形图数据
- // 添加一个可选参数overrideKnowledgeId,用于临时覆盖当前的currentKnowledgeId
- const generateTreeData = (data, level = 0, overrideKnowledgeId = null) => {
- // 使用传入的overrideKnowledgeId或默认使用vm.selectedKnowledgeId
- const currentKnowledgeId = overrideKnowledgeId !== null ? overrideKnowledgeId : vm.selectedKnowledgeId;
- return data.map(item => {
- // 检查当前节点是否匹配选中的知识点ID
- const isMatched = String(item.knowledgeId) === String(currentKnowledgeId);
- // 获取节点级别,优先使用对应级别的得分率,若缺失则使用另一级别作为备选
- const scoreRate = item.personalScoreRate;
- const hasData = scoreRate !== undefined && scoreRate !== null && scoreRate !== '';
-
- // 设置节点大小:有数据的默认10px,选中后15px;无数据的5px
- let symbolSize = 5;
- if (hasData) {
- symbolSize = isMatched ? 15 : 10;
- }
- // 计算节点样式
- const itemColor = hasData ? vm.getNodeColor(scoreRate) : '#BFC1C7';
-
- // 获取节点级别用于图例过滤
- const nodeLevel = getNodeLevel(scoreRate);
- // 应用图例过滤
- const shouldShow = vm.selectedLegend[nodeLevel];
-
- // 设置节点透明度:显示为1,隐藏为0
- const opacity = shouldShow ? 1 : 0;
-
- const node = {
- name: item.knowledgeName,
- symbolSize: symbolSize,
- // 使用已计算的scoreRate作为value,确保数据一致性
- value: scoreRate,
- itemStyle: {
- color: itemColor,
- opacity: opacity
- },
- // 当节点被隐藏时,隐藏连接到该节点的线
- lineStyle: {
- opacity: shouldShow ? 1 : 0
- },
- // 保存知识ID用于匹配
- 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 = () => {
- const screenWidth = window.innerWidth;
- let zoom = 1.05;
-
- // 根据屏幕宽度调整缩放
- if (screenWidth < 1100) {
- zoom = 1.05;
- } else if (screenWidth >= 1100 && screenWidth <= 1200) {
- zoom = 1.05;
- } else {
- zoom = 1.05;
- }
-
- // 根据节点数量调整缩放,节点少的时候适当放大
- if (outermostCount <= 5) {
- zoom *= 1.0;
- }
-
- 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;
- }
- // 检查节点是否为最外层节点(叶子节点)
- // 使用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;
- }
- }
- },
- 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, // 添加value属性,用于显示得分率
- itemStyle: {
- // 根节点为非最外层节点,使用#EBEEF5颜色
- color: '#BFC1C7'
- },
- // 标记为非最外层节点
- isOutermost: 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);
- // 初始化后调用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', () => {
- if (this.chart) {
- this.chart.resize();
- this.updateChartLayout();
- }
- });
- }
- catch (error) {
- console.error('图表创建失败:', error);
- }
- },
- // 更新图表数据
- updateChart() {
- try {
- if (this.activeView === 'graph' && this.$refs.chart) {
- // 重新创建图表,实现刷新效果
- this.createChart();
- }
- }
- catch (error) {
- console.error('图表更新失败:', error);
- }
- },
- // 根据得分率获取对应的样式类
- 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 '薄弱';
- }
- },
- // 行样式类名方法 - vxe-table 3.7.5版本兼容
- rowClassName({ row }) {
- // 使用selectedRow引用比较,避免修改数据
- if (this.selectedRow && this.selectedRow.knowledgeId === row.knowledgeId) {
- return 'selected-row';
- }
- return row.highlight ? 'row_highlight' : '';
- },
- // 处理知识点列表项点击事件
- 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();
- }
- },
- // 处理树形表格行点击事件
- handleRowClick(row) {
- // 检查是否为最后一级节点(没有children或children为空)
- const isLastLevel = !row.children || row.children.length === 0;
- if (isLastLevel) {
- // 设置当前行为选中行,不修改数据,只更新引用
- this.selectedRow = row;
- // 调用handleItemClick方法处理点击事件
- this.handleItemClick(row, -1);
- }
- },
- // 处理单元格点击事件
- handleCellClick({ row }) {
- // 调用行点击事件处理逻辑
- this.handleRowClick(row);
- },
- // 根据知识点得分率获取对应的点颜色
- getDotColor(item, scoreRateType) {
- // 个人得分率personalScoreRate
- const scoreRate = scoreRateType === 'personalScoreRate' ? item.personalScoreRate : item.classScoreRate;
- const rate = parseFloat(scoreRate);
- if (rate >= 85) {
- return '#3BA272'; // 优秀 - 绿色
- } else if (rate >= 60 && rate < 84) {
- return '#FAC858'; // 良好 - 黄色
- } else if (rate < 59) {
- return '#EE6666'; // 薄弱 - 红色
- }
- }
- }
- };
- </script>
- <style lang="scss" scoped>
- .knowledge_graph {
- background: #FFFFFF;
- border-radius: 10px;
- padding: 20px;
- margin-bottom: 10px;
- position: relative;
- /* 选中行样式 - 确保所有情况下都能正确应用 */
- :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;
- opacity: 0.7;
- transition: all 0.3s ease;
- font-weight: 500;
- &.selected {
- opacity: 1;
- }
- &:hover {
- opacity: 1;
- }
- .legend_dot {
- width: 20px;
- height: 10px;
- border-radius: 2px;
- transition: all 0.3s ease;
- background: #999999; // 默认灰色
- &.weak {
- background: #EE6666;
- }
- &.good {
- background: #FAC858;
- }
- &.excellent {
- background: #3BA272;
- }
- }
- .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;
- }
- }
- }
- }
- }
- // 视图切换按钮样式
- .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%);
- }
- }
- // 对比选择器样式
- .comparison_selector {
- display: flex;
- align-items: center;
- gap: 10px;
- .student_position {
- font-size: 14px;
- color: #999999;
- }
- // 对比选择器按钮样式
- :deep(.el-button-group) {
- .el-button {
- padding: 7px 10px;
- // border-radius: 4px;
- &:not(.el-button--primary) {
- color: #999999;
- background-color: #FFFFFF;
- border-color: #DCDFE6;
- &:hover {
- color: #2E64FA;
- border-color: #C6E2FF;
- }
- }
- &.el-button--primary {
- background-color: #2E64FA;
- color: #FFFFFF;
- border-color: #2E64FA;
- &:hover {
- background-color: #409EFF;
- border-color: #409EFF;
- }
- }
- }
- }
- }
- .graph_content {
- display: flex;
- gap: 20px;
- position: relative;
- .knowledge_tab {
- display: flex;
- border-bottom: 1px solid #ECEEF3;
- margin-bottom: 15px;
- .tab_item {
- height: 40px;
- line-height: 40px;
- cursor: pointer;
- font-size: 14px;
- color: #666666;
- position: relative;
- transition: all 0.3s ease;
- margin-right: 20px;
- &.active {
- color: #2E64FA;
- font-weight: 500;
- &::after {
- content: '';
- position: absolute;
- bottom: -1px;
- left: 0;
- width: 100%;
- height: 2px;
- background-color: #2E64FA;
- }
- }
- &:hover {
- color: #2E64FA;
- }
- // 零分知识点:根据文字宽度自适应,不需要内间距
- // &:first-child {
- // padding: 0;
- // white-space: nowrap;
- // }
- // // 高频错题知识点:占满剩余宽度
- // &:last-child {
- // padding: 0;
- // white-space: nowrap;
- // }
- }
- }
- .knowledge_right_container {
- width: 350px;
- display: flex;
- flex-direction: column;
- }
- .chart_container {
- flex: 1;
- min-width: 0;
- border-radius: 10px 10px 10px 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;
- opacity: 0.7;
- transition: all 0.3s ease;
- font-weight: 500;
- &.selected {
- opacity: 1;
- }
- &:hover {
- opacity: 1;
- }
- .legend_dot {
- width: 20px;
- height: 10px;
- border-radius: 2px;
- transition: all 0.3s ease;
- background: #999999; // 默认灰色
- &.weak {
- background: #EE6666;
- }
- &.good {
- background: #FAC858;
- }
- &.excellent {
- background: #3BA272;
- }
- }
- .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;
- }
- .view_switcher_container {
- display: flex;
- gap: 20px;
- z-index: 10;
- justify-content: flex-end;
- font-size: 14px;
- white-space: nowrap; // 防止按钮换行
- }
- }
- .knowledge_list {
- width: 100%;
- height: 630px;
- 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;
- }
- /* 暂无数据样式 */
- .no_data {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- font-size: 14px;
- color: #909399;
- text-align: center;
- width: 100%;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 8px;
- span {
- margin-top: 23%;
- }
- }
- .list_item {
- background: rgba(255, 255, 255, 0.1);
- border-radius: 6px;
- padding: 10px;
- margin-bottom: 10px;
- cursor: pointer;
- &:last-child {
- margin-bottom: 0;
- }
- &:hover,
- &.active {
- background: rgba(46, 100, 250, 0.1);
- border-radius: 6px;
- }
- .item_header {
- display: flex;
- align-items: center;
- margin-bottom: 8px;
- .item_dot {
- display: inline-block;
- width: 8px;
- height: 8px;
- background: #EE6666;
- border-radius: 50%;
- margin-right: 8px;
- }
- .item_title {
- font-size: 14px;
- font-weight: 500;
- color: #333;
- flex: 1;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- min-width: 0;
- }
- .item_tag {
- color: #FFFFFF;
- font-size: 12px;
- padding: 4px 6px;
- margin-left: 5px;
- text-align: center;
- border-radius: 4px;
- font-weight: 400;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 4px;
- i:last-child {
- display: block;
- }
- }
- }
- .item_info {
- font-size: 12px;
- color: #999;
- margin-top: 8px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- .item_score {
- text-align: left;
- .score_label {
- color: #999999;
- }
- .score_first {
- color: #F56C6C;
- }
- .score_separator {
- color: #999999;
- margin: 0 4px;
- }
- .score_second {
- color: #666666;
- }
- }
- .item_exam_count {
- text-align: right;
- color: #999999;
- .exam_count {
- color: #2E64FA;
- }
- }
- }
- }
- }
- }
- // 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;
- // 使用CSS变量设置全局行高
- --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;
- }
- }
- }
- }
- // 单元格样式,设置内边距为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 {
- 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 {
- // display: flex;
- 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;
- }
- .diff_value {
- font-size: 14px;
- color: #606266;
- &.diff_negative {
- color: #F56C6C;
- }
- }
- }
- }
- </style>
|