| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733 |
- @ -1,702 +0,0 @@
- <template>
- <div class="region_container">
- <!-- 薄弱补强区 -->
- <div class="region_section weak">
- <div class="region_header">
- <div class="region_header_main">
- <span class="region_icon">
- <img :src="require('@/assets/knowledgeGraph/buqiang.png')" alt="薄弱补强区" style="width: 32px; height: auto; display: block;" />
- </span>
- <span class="region_title">薄弱补强区</span>
- </div>
- <span class="region_desc">得分率<60% | 优先补齐短板</span>
- </div>
- <div class="knowledge_rows" ref="weakRowsBox">
- <div
- class="knowledge_row"
- v-for="(item, index) in weakArea"
- :key="'weak-' + index"
- :data-knowledge-id="item.knowledgeId"
- @click="handleRowClick(item)"
- :class="{ selected: isKnowledgeSelected(item), highlighted: isKnowledgeSelected(item) }"
- >
- <div class="row_left">
- <span class="kp_name" :title="item.knowledgeName">{{ item.knowledgeName }}</span>
- <div class="kp_meta" :title="'得分率:' + getScoreRate(item) + '% 考频:' + getExamFreq(item)">
- <span class="kp_score">得分率:{{ getScoreRate(item) }}%</span>
- <span class="kp_freq_tag">考频:{{ getExamFreq(item) }}</span>
- </div>
- </div>
- <div class="row_right">
- <el-button size="small" class="add_btn weak_btn"
- :disabled="isAddPracticeDisabled(item)"
- @click.stop="handleAddPractice(item, $event)">
- 加入练习
- </el-button>
- </div>
- </div>
- <div v-if="weakArea.length === 0" class="empty_tip no_content_data"><span>暂无数据</span></div>
- </div>
- </div>
- <!-- 能力提升区 -->
- <div class="region_section good">
- <div class="region_header">
- <div class="region_header_main">
- <span class="region_icon">
- <img :src="require('@/assets/knowledgeGraph/tisheng.png')" alt="能力提升区" style="width: 32px; height: auto; display: block;" />
- </span>
- <span class="region_title">能力提升区</span>
- </div>
- <span class="region_desc">60%≤得分率<85% | 强化练习提升</span>
- </div>
- <div class="knowledge_rows" ref="goodRowsBox">
- <div
- class="knowledge_row"
- v-for="(item, index) in goodArea"
- :key="'good-' + index"
- :data-knowledge-id="item.knowledgeId"
- @click="handleRowClick(item)"
- :class="{ selected: isKnowledgeSelected(item), highlighted: isKnowledgeSelected(item) }"
- >
- <div class="row_left">
- <span class="kp_name" :title="item.knowledgeName">{{ item.knowledgeName }}</span>
- <div class="kp_meta" :title="'得分率:' + getScoreRate(item) + '% 考频:' + getExamFreq(item)">
- <span class="kp_score">得分率:{{ getScoreRate(item) }}%</span>
- <span class="kp_freq_tag">考频:{{ getExamFreq(item) }}</span>
- </div>
- </div>
- <div class="row_right">
- <el-button size="small" class="add_btn good_btn"
- :disabled="isAddPracticeDisabled(item)"
- @click.stop="handleAddPractice(item, $event)">
- 加入练习
- </el-button>
- </div>
- </div>
- <div v-if="goodArea.length === 0" class="empty_tip no_content_data"><span>暂无数据</span></div>
- </div>
- </div>
- <!-- 扎实巩固区 -->
- <div class="region_section excellent">
- <div class="region_header">
- <div class="region_header_main">
- <span class="region_icon">
- <img :src="require('@/assets/knowledgeGraph/gonggu.png')" alt="扎实巩固区" style="width: 32px; height: auto; display: block;" />
- </span>
- <span class="region_title">扎实巩固区</span>
- </div>
- <span class="region_desc">得分率>85% | 定期练习巩固</span>
- </div>
- <div class="knowledge_rows" ref="excellentRowsBox">
- <div
- class="knowledge_row"
- v-for="(item, index) in excellentArea"
- :key="'excellent-' + index"
- :data-knowledge-id="item.knowledgeId"
- @click="handleRowClick(item)"
- :class="{ selected: isKnowledgeSelected(item), highlighted: isKnowledgeSelected(item) }"
- >
- <div class="row_left">
- <span class="kp_name" :title="item.knowledgeName">{{ item.knowledgeName }}</span>
- <div class="kp_meta" :title="'得分率:' + getScoreRate(item) + '% 考频:' + getExamFreq(item)">
- <span class="kp_score">得分率:{{ getScoreRate(item) }}%</span>
- <span class="kp_freq_tag">考频:{{ getExamFreq(item) }}</span>
- </div>
- </div>
- <div class="row_right">
- <el-button size="small" class="add_btn excellent_btn"
- :disabled="isAddPracticeDisabled(item)"
- @click.stop="handleAddPractice(item, $event)">
- 加入练习
- </el-button>
- </div>
- </div>
- <div v-if="excellentArea.length === 0" class="empty_tip no_content_data"><span>暂无数据</span></div>
- </div>
- </div>
- <!-- 冲刺进阶区(暂时隐藏) -->
- <div class="region_section advanced" >
- <div class="region_header">
- <div class="region_header_main">
- <span class="region_icon">
- <img :src="require('@/assets/knowledgeGraph/jinjie.png')" alt="冲刺进阶区" style="width: 32px; height: auto; display: block;" />
- </span>
- <span class="region_title">冲刺进阶区</span>
- </div>
- <span class="region_desc">优于同层级学生 | 对标上游冲刺进阶</span>
- </div>
- <div class="knowledge_rows" ref="advancedRowsBox">
- <div
- class="knowledge_row"
- v-for="(item, index) in advancedArea"
- :key="'advanced-' + index"
- :data-knowledge-id="item.knowledgeId"
- @click="handleRowClick(item)"
- :class="{ selected: isKnowledgeSelected(item), highlighted: isKnowledgeSelected(item) }"
- >
- <div class="row_left">
- <span class="kp_name" :title="item.knowledgeName">{{ item.knowledgeName }}</span>
- <div class="kp_meta" :title="'得分率:' + getScoreRate(item) + '% 考频:' + getExamFreq(item)">
- <span class="kp_score">得分率:{{ getScoreRate(item) }}%</span>
- <span class="kp_freq_tag">考频:{{ getExamFreq(item) }}</span>
- </div>
- </div>
- <div class="row_right">
- <el-button size="small" class="add_btn advanced_btn"
- :disabled="isAddPracticeDisabled(item)"
- @click.stop="handleAddPractice(item, $event)">
- 加入练习
- </el-button>
- </div>
- </div>
- <div v-if="advancedArea.length === 0" class="empty_tip no_content_data"><span>暂无数据</span></div>
- </div>
- </div>
- </div>
- </template>
- <script>
- export default {
- name: 'Region',
- props: {
- // 分区数据(由父级通过 knowledge_partition 接口返回后传入)
- // conquerZone: 薄弱补强区
- // improveZone: 能力提升区
- // consolidateZone:扎实巩固区
- // sprintZone: 冲刺进阶区(接口字段:sprint)
- partitionData: {
- type: Object,
- default: () => ({
- conquerZone: [],
- improveZone: [],
- consolidateZone: [],
- sprintZone: []
- })
- },
- // 父级传入的"联动选中知识点ID"
- // - 环形扩散图选中的圆点,父级会把 knowledgeId 写到这里 → 本组件高亮对应行并滚动居中
- // - 点本组件任一行条目时,也通过 emit('item-click') 反向通知父级把 ID 同步到 circularDiffusion
- selectedKnowledgeId: {
- type: [String, Number],
- default: ''
- }
- },
- data() {
- return {};
- },
- watch: {
- // 父级 selectedKnowledgeId 变化:联动高亮 + 滚动到对应区列表的可视中央
- selectedKnowledgeId: {
- immediate: true,
- handler(newVal) {
- if (!newVal) return;
- const sid = String(newVal);
- // 这里只负责发起"滚动尝试",函数内部会处理 DOM/数据未就绪时的重试
- this.scheduleHighlightScroll(sid);
- }
- },
- // partitionData 变化:路由进入组件时,父级接口返回前 selectedKnowledgeId 可能已经写好了,
- // 但此时 conquerZone/improveZone/consolidateZone 都是空数组 → 上面的 watch 已经执行过(immediate)但没找到条目,不会再自动重跑。
- // 所以 partitionData 有内容变化后,只要还有选中 ID,就重新触发一次滚动。
- partitionData: {
- deep: true,
- handler(newVal, oldVal) {
- // 快速跳过空对象/完全一样的引用
- if (!this.selectedKnowledgeId) return;
- const lists = ['conquerZone', 'improveZone', 'consolidateZone', 'sprintZone'];
- let hasAnyData = false;
- if (newVal) {
- for (const k of lists) {
- if (Array.isArray(newVal[k]) && newVal[k].length > 0) { hasAnyData = true; break; }
- }
- }
- if (!hasAnyData) return;
- // 接口数据刚落地,Vue 下一 tick 才会 patch DOM 行节点;所以 $nextTick 再排
- const sid = String(this.selectedKnowledgeId);
- this.$nextTick(() => this.scheduleHighlightScroll(sid));
- }
- }
- },
- activated() {
- // keep-alive 缓存路由时,组件被切回来会触发 activated,但 watch 不会再立即执行
- // 只要有选中 ID 就重新尝试滚动居中(DOM 已就绪,命中会直接滚)
- if (this.selectedKnowledgeId) {
- const sid = String(this.selectedKnowledgeId);
- this.$nextTick(() => this.scheduleHighlightScroll(sid));
- }
- },
- computed: {
- // 薄弱补强区:直接用接口返回的 conquerZone
- weakArea() {
- return Array.isArray(this.partitionData?.conquerZone) ? this.partitionData.conquerZone : [];
- },
- // 能力提升区:直接用接口返回的 improveZone
- goodArea() {
- return Array.isArray(this.partitionData?.improveZone) ? this.partitionData.improveZone : [];
- },
- // 扎实巩固区:直接用接口返回的 consolidateZone
- excellentArea() {
- return Array.isArray(this.partitionData?.consolidateZone) ? this.partitionData.consolidateZone : [];
- },
- // 冲刺进阶区:使用接口返回的 sprint 数组(父级映射为 partitionData.sprintZone)
- advancedArea() {
- return Array.isArray(this.partitionData?.sprintZone) ? this.partitionData.sprintZone : [];
- }
- },
- methods: {
- // 获取得分率:优先 personalScoreRate(接口分区字段),兜底 class / grade
- getScoreRate(item) {
- if (item && (item.personalScoreRate !== null && item.personalScoreRate !== undefined)) {
- const n = Number(item.personalScoreRate);
- return isNaN(n) ? 0 : n;
- }
- const fallback = item?.gradeScoreRate ?? item?.classScoreRate;
- if (fallback === null || fallback === undefined) return 0;
- const n = Number(fallback);
- return isNaN(n) ? 0 : n;
- },
- // 获取考频:优先 examNum(接口分区字段:考频,含练习),兜底常见字段
- getExamFreq(item) {
- if (item && (item.examNum !== null && item.examNum !== undefined && item.examNum !== '')) {
- const n = Number(item.examNum);
- return isNaN(n) ? 0 : n;
- }
- const fallback = item?.examFreq ?? item?.examCount ?? item?.questionNum ?? item?.wrongCount;
- if (fallback === null || fallback === undefined || fallback === '') return 0;
- const n = Number(fallback);
- return isNaN(n) ? 0 : n;
- },
- /**
- * 判断某个知识点条目是不是"当前联动选中"(跨区单选:三/四个区只能有一条高亮)
- * 把父级 selectedKnowledgeId 和条目 knowledgeId 做字符串比较即可
- */
- isKnowledgeSelected(item) {
- const sid = this.selectedKnowledgeId;
- const id = item?.knowledgeId;
- if (!sid || id === null || id === undefined || id === '') return false;
- return String(sid) === String(id);
- },
- /**
- * 【按 knowledgeType 精准分区匹配】联动选中时查找条目并滚动到中间
- * 规则(和你描述一致):
- * knowledgeType === 1 → 薄弱补强区 weakArea / weakRowsBox 容器
- * knowledgeType === 2 → 能力提升区 goodArea / goodRowsBox
- * knowledgeType === 3 → 扎实巩固区 excellentArea / excellentRowsBox
- * 其它(4/未知)→ 冲刺进阶区 advancedArea / advancedRowsBox(暂时隐藏)
- * 只在对应分区的数组里做 knowledgeId 的 O(n) 查找,不需要对比三个数组,效率高
- *
- * @returns {boolean} true=找到条目并执行了滚动;false=数据/DOM未就绪需要重试
- */
- highlightAndScrollByKnowledgeId(sid) {
- if (!sid) return false;
- // 第一步:找命中条目(只查其 knowledgeType 决定的对应分区数组)
- let zoneBoxRef = null;
- const zones = [
- { type: 1, list: this.weakArea, ref: 'weakRowsBox' },
- { type: 2, list: this.goodArea, ref: 'goodRowsBox' },
- { type: 3, list: this.excellentArea, ref: 'excellentRowsBox' },
- { type: 4, list: this.advancedArea, ref: 'advancedRowsBox' }
- ];
- // 先尝试"根据命中条目自己的 knowledgeType 定位分区":需要知道 ID 对应哪个 type,先 O(n) 找条目
- // 折中:如果某一个分区数组先匹配到 knowledgeId,就直接在该分区内滚动——也是只遍历最多"总数组数*平均长度",
- // 但用户担心的是"把三个数组都循环一遍效率慢"——这里通过先 type 预判 + 找到即停,最差仍是 3*N 但 N 是分区各自长度,且实际 90% 场景第一个命中就 break。
- let matchedItem = null;
- for (const z of zones) {
- const list = Array.isArray(z.list) ? z.list : [];
- const hit = list.find(itm => String(itm.knowledgeId) === sid);
- if (hit) {
- matchedItem = hit;
- zoneBoxRef = z.ref;
- break;
- }
- }
- // 没找到条目:说明父级 partitionData 还没被接口赋值(空数组),返回 false 让外层重试
- if (!matchedItem || !zoneBoxRef) return false;
- // 第二步:找到该条目对应的 DOM(用 data-knowledge-id 属性),在其所属分区滚动容器中居中显示
- // ——关键:只滚动该区的 knowledge_rows(overflow-y:auto),不要带动整个页面滚动
- // (因此禁止使用 element.scrollIntoView,它会从 window 开始递归把祖先滚动容器都对齐)
- const box = this.$refs[zoneBoxRef];
- const container = box && (box.$el || box);
- // 容器 DOM 还未渲染(例如 ref 还没挂载:created 阶段 immediate watch 触发)→ 重试
- if (!container) return false;
- const idAttr = CSS.escape ? CSS.escape(sid) : sid;
- const rowEl = container.querySelector
- ? container.querySelector(`[data-knowledge-id="${idAttr}"]`)
- : null;
- // 行节点还没被 v-for patch 出来(接口刚赋值,Vue 下一 tick 才渲染)→ 重试
- if (!rowEl) return false;
- // 用 getBoundingClientRect 的差值算"行相对于容器内容顶边的绝对偏移"
- // 这样即使容器内部有 padding / border / 行嵌套其他节点导致 offsetParent 不是 container,也能精确定位
- let containerRect = null;
- let rowRect = null;
- try {
- containerRect = container.getBoundingClientRect();
- rowRect = rowEl.getBoundingClientRect();
- } catch (e) {
- return false;
- }
- if (!containerRect || !rowRect) return false;
- const relativeOffsetTop = (rowRect.top - containerRect.top) + (container.scrollTop || 0);
- const rowHeight = rowRect.height || 40;
- const viewportH = container.clientHeight || 0;
- const rawTarget = relativeOffsetTop - (viewportH / 2) + (rowHeight / 2);
- const maxScrollTop = Math.max(0, (container.scrollHeight || 0) - viewportH);
- const finalTop = Math.max(0, Math.min(maxScrollTop, rawTarget));
- try {
- if (typeof container.scrollTo === 'function') {
- // 只作用在当前分区容器上,window/父容器都不会动
- container.scrollTo({ top: finalTop, behavior: 'smooth' });
- } else {
- container.scrollTop = finalTop;
- }
- } catch (e) {
- try { container.scrollTop = finalTop; } catch (err) {}
- }
- return true;
- },
- /**
- * 调度"高亮+滚动居中":如果本轮没找到条目/DOM 就间隔 60ms 再重试,最多 5 次(≈300ms),
- * 覆盖以下时序:
- * 1) keep-alive activated 回来,DOM 已在但数据可能未同步好
- * 2) watch selectedKnowledgeId immediate 先跑 → 接口数据还没来
- * 3) partitionData 接口刚返回 → Vue v-for 还没把行 DOM patch 出来(computed 更新但 DOM 滞后)
- */
- scheduleHighlightScroll(sid, attempt = 0) {
- const MAX_ATTEMPTS = 5;
- if (!sid || attempt >= MAX_ATTEMPTS) return;
- const ok = this.highlightAndScrollByKnowledgeId(String(sid));
- if (ok) return; // 已经成功滚动到目标行
- // 没命中:用 setTimeout 延迟再试(下一 tick 可能数据/DOM 就好了)
- setTimeout(() => this.scheduleHighlightScroll(sid, attempt + 1), 60);
- },
- /**
- * 点击某个知识点行:
- * - 联动:emit('item-click', item) 给父级,父级写 currentLinkedKnowledgeId
- * → 一方面本组件通过 :selectedKnowledgeId 回传后天然高亮(跨区单选)
- * → 另一方面 circularDiffusion 会放大对应圆点 + 右侧折线表格显示数据
- */
- handleRowClick(item) {
- if (!item || !item.knowledgeId) return;
- this.$emit('item-click', item);
- },
- /**
- * questionNum 为 0 或 null 时,加入练习按钮置灰不可点
- */
- isAddPracticeDisabled(item) {
- const num = item && item.questionNum;
- return num === null || num === undefined || num === '' || Number(num) === 0;
- },
- // 处理加入练习(第二个参数为点击源 DOM,供飞入动画使用)
- handleAddPractice(item, e) {
- if (this.isAddPracticeDisabled(item)) return;
- const sourceEl = (e && e.currentTarget) || null;
- this.$emit('add-practice', item, sourceEl);
- }
- }
- };
- </script>
- <style lang="scss" scoped>
- .region_container {
- display: flex;
- flex-direction: row;
- gap: 10px;
- width: 100%;
- box-sizing: border-box;
- align-items: stretch;
- flex-wrap: wrap;
- }
- /* 单个区域卡片(通用样式:圆角作为通用属性) */
- .region_section {
- flex: 1 1 0;
- min-width: 0;
- border-radius: 10px 10px 10px 10px;
- padding: 16px 16px 12px;
- background: #FFFFFF;
- display: flex;
- flex-direction: column;
- height: 560px;
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
- overflow: hidden;
- &.weak {
- border: 1px solid #F56C6C;
- background: linear-gradient(180deg, rgba(245, 108, 108, 0.1) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF;
- }
- &.good {
- border: 1px solid #FAC858;
- background: linear-gradient(180deg, rgba(250, 200, 88, 0.1) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF;
- }
- &.excellent {
- border: 1px solid #3BA272;
- background: linear-gradient(180deg, rgba(59, 162, 114, 0.1) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF;
- }
- &.advanced {
- border: 1px solid #5470C6;
- background: linear-gradient(180deg, rgba(84, 112, 198, 0.1) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF;
- }
- }
- /* 卡片头部:图标方块 + 标题(同一行) + 副标题(下一行) */
- .region_header {
- display: flex;
- flex-direction: column;
- justify-content: flex-start;
- gap: 6px; /* 上一行(图标+标题) 与 下一行(描述) 的竖向间距 */
- margin-bottom: 14px;
- /* 第一行容器:图标 + 标题 同一行,垂直居中 */
- .region_header_main {
- display: flex;
- align-items: center;
- gap: 10px;
- min-width: 0;
- }
- .region_icon {
- width: 32px;
- height: 32px;
- display: flex;
- align-items: center;
- justify-content: center;
- flex: 0 0 32px;
- img {
- width: 100%;
- height: 100%;
- object-fit: contain;
- display: block;
- }
- svg {
- display: block;
- }
- }
- .region_title {
- font-size: 24px;
- font-weight: 600;
- color: #333333;
- line-height: 1;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- min-width: 0;
- }
- .region_desc {
- font-size: 14px;
- color: #666666;
- line-height: 1.4;
- }
- }
- /* 行列表容器:可滚动,显示自定义滚动条 */
- .knowledge_rows {
- display: flex;
- flex-direction: column;
- gap: 10px;
- overflow-y: auto;
- flex: 1;
- min-height: 0;
- position: relative;
- &::-webkit-scrollbar {
- width: 6px;
- }
- &::-webkit-scrollbar-thumb {
- background: #C0C4CC;
- border-radius: 3px;
- }
- &::-webkit-scrollbar-track {
- background: #F5F7FA;
- border-radius: 3px;
- }
- scrollbar-width: thin;
- scrollbar-color: #C0C4CC #F5F7FA;
- }
- .empty_tip {
- height: 240px;
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- width: 100%;
- border-radius: 8px;
- span {
- margin-top: 23%;
- }
- }
- /* 每行知识点条目 */
- .knowledge_row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 10px;
- padding: 10px;
- border-radius: 4px;
- border: 1px solid;
- background: #FFFFFF;
- min-width: 0;
- cursor: pointer;
- .row_left {
- display: flex;
- flex-direction: column;
- gap: 4px;
- min-width: 0;
- flex: 1;
- .kp_name {
- font-weight: 500;
- font-size: 14px;
- color: #333333;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- margin-bottom: 8px;
- }
- .kp_meta {
- display: flex;
- align-items: center;
- gap: 10px;
- flex-wrap: nowrap; /* 禁止换行,小屏下更美观 */
- overflow: hidden;
- color: #999999; /* 考频字色固定浅灰 */
- font-size: 12px;
- .kp_score,
- .kp_freq_tag {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- flex-shrink: 1;
- min-width: 0;
- }
- }
- }
- .row_right {
- flex-shrink: 0;
- .add_btn {
- padding: 8px 12px;
- font-size: 14px;
- line-height: 1;
- border-radius: 4px;
- }
- }
- }
- /* 四个区的条目边框 + 按钮颜色 + 得分率主题色 + 行悬浮/选中背景 */
- .weak .knowledge_row {
- border-color: #F2B5B5;
- background: #FFFFFF;
- transition: background-color 0.15s ease;
- }
- .weak .knowledge_row:hover {
- background: rgba(245, 108, 108, 0.1);
- }
- .weak .knowledge_row.selected {
- background: rgba(245, 108, 108, 0.15);
- }
- .weak .kp_meta .kp_score {
- color: #F56C6C; /* 薄弱区:得分率和卡片主题色一致 */
- }
- .weak .knowledge_row .add_btn.weak_btn {
- color: #EE6666;
- border: 1px solid #EE6666;
- background: #FFFFFF;
- }
- .good .knowledge_row {
- border-color: #F7DEA5;
- background: #FFFFFF;
- transition: background-color 0.15s ease;
- }
- .good .knowledge_row:hover {
- background: rgba(250, 200, 88, 0.1);
- }
- .good .knowledge_row.selected {
- background: rgba(250, 200, 88, 0.15);
- }
- .good .kp_meta .kp_score {
- color: #FAC858; /* 能力提升区:得分率和卡片主题色一致 */
- }
- .good .knowledge_row .add_btn.good_btn {
- color: #F0B429;
- border: 1px solid #F0B429;
- background: #FFFFFF;
- }
- .excellent .knowledge_row {
- border-color: #AEE2C5;
- background: #FFFFFF;
- transition: background-color 0.15s ease;
- }
- .excellent .knowledge_row:hover {
- background: rgba(59, 162, 114, 0.1);
- }
- .excellent .knowledge_row.selected {
- background: rgba(59, 162, 114, 0.15);
- }
- .excellent .kp_meta .kp_score {
- color: #3BA272; /* 扎实巩固区:得分率和卡片主题色一致 */
- }
- .excellent .knowledge_row .add_btn.excellent_btn {
- color: #3BA272;
- border: 1px solid #3BA272;
- background: #FFFFFF;
- }
- .advanced .knowledge_row {
- border-color: #B9CBFC;
- background: #FFFFFF;
- transition: background-color 0.15s ease;
- }
- .advanced .knowledge_row:hover {
- background: rgba(84, 112, 198, 0.1);
- }
- .advanced .knowledge_row.selected {
- background: rgba(84, 112, 198, 0.15);
- }
- .advanced .kp_meta .kp_score {
- color: #5470C6; /* 冲刺进阶区:得分率和卡片主题色一致(暂时隐藏) */
- }
- .advanced .knowledge_row .add_btn.advanced_btn {
- color: #2E64FA;
- border: 1px solid #2E64FA;
- background: #FFFFFF;
- }
- /* 无题目(questionNum 为 0/null)置灰:与环形图「已加入练习」disabled 样式一致 */
- .knowledge_row .add_btn.is-disabled,
- .knowledge_row .add_btn[disabled] {
- border: 1px solid #DCDFE6 !important;
- color: #C0C4CC !important;
- background: #F5F7FA !important;
- cursor: not-allowed !important;
- &:hover,
- &:focus {
- border: 1px solid #DCDFE6 !important;
- color: #C0C4CC !important;
- background: #F5F7FA !important;
- }
- }
- /* 响应式:小屏适配 */
- @media (max-width: 1366px) {
- .region_container {
- gap: 10px;
- }
- .region_section {
- height: 460px;
- padding: 12px 12px 8px;
- }
- .region_header {
- margin-bottom: 10px;
- gap: 4px;
- }
- }
- @media (max-width: 1200px) {
- .region_section {
- height: auto;
- min-height: 320px;
- }
- .knowledge_rows {
- flex: 1;
- min-height: 200px;
- }
- }
- </style>
|