| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907 |
- <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' }">
- {{ item.scoreRateDiff }}%
- </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 => {
- // 根据节点是否为最外围节点
- const isOutermost = !item.children || item.children.length === 0;
- // 检查当前节点是否匹配选中的知识点ID
- const isMatched = isOutermost && String(item.knowledgeId) === String(currentKnowledgeId);
- // 设置节点大小:选中状态16px,未选中状态10px,非叶子节点5px
- let symbolSize = 5;
- if (isOutermost) {
- symbolSize = isMatched ? 15 : 10;
- }
- // 获取节点级别,优先使用对应级别的得分率,若缺失则使用另一级别作为备选
- const scoreRate = item.personalScoreRate;
- const nodeLevel = getNodeLevel(scoreRate);
-
- // 计算节点样式,使用与nodeLevel相同的得分率逻辑
- const itemColor = isOutermost ? vm.getNodeColor(scoreRate) : '#BFC1C7';
- // 仅对最外层节点应用图例过滤,非外层节点始终显示
- const shouldShow = !isOutermost || 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
- },
- // 添加isOutermost属性用于tooltip判断
- isOutermost: isOutermost,
- // 保存知识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: true,
-
- // 使用边距控制图表位置,实现水平垂直居中
- left: '8%', // 左边距(百分比/像素)
- right: '8%', // 右边距
- top: '8%', // 上边距
- bottom: '8%', // 下边距
- // 根据节点数量动态调整内外半径,防止节点遮盖
- radius: getOptimalRadius(),
-
- // 动态计算合适的缩放比例,根据屏幕宽度和节点数量调整
- zoom: getOptimalZoom(),
- scaleLimit: {
- min: 0.2,
- max: 5
- },
- // 调整节点大小比例和间距,确保节点不遮盖
- nodeScaleRatio: 1,
- // 根据节点数量调整层级间距,节点少的时候增大间距
- layerPadding: outermostCount <= 5 ? [20, 10] : [10, 5],
- roam: true,
- // 使用表格数据生成树形结构
- 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 {
- margin-left: 5px;
- color: #FFFFFF;
- font-size: 12px;
- padding: 4px 6px;
- border-radius: 4px;
- font-weight: 400;
- }
- }
- .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>
|