news 2026/10/27 1:39:39

【口算王|04】HarmonyOS ArkTS 错题反馈实战:显示计算步骤并记录错误类型

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【口算王|04】HarmonyOS ArkTS 错题反馈实战:显示计算步骤并记录错误类型

【口算王|04】HarmonyOS ArkTS 错题反馈实战:显示计算步骤并记录错误类型

证据边界:本文的“当前实现”来自本轮对本地口算王工程中ExamResultPage.ets、PracticePage.ets、MathModels.ets、MockBanks.ets与UserDataManager.ets的静态复核。可以确认答题记录、错题本、结果页复盘路由和静态解析链路;结构化计算步骤、错误类型、题目级耗时和诊断置信度均属于建议扩展。本轮未执行构建、安装、真机复盘、持久化故障或多设备视觉测试。

口算应用的“错题反馈”很容易被做成一句红色提示:答案错了,正确答案是 B。这个反馈虽然即时,却没有回答真正影响下一次练习的问题:用户错在符号识别、运算顺序、进位退位,还是只点错了选项?如果结果页只保存一个correct=false,复盘页就只能重复展示题目,无法形成有针对性的训练。

本文基于口算王项目本地口算王工程(脱敏路径)的真实源码,复核ExamResultPage.ets、PracticePage.ets、MathModels.ets、MockBanks.ets与UserDataManager.ets。文中的当前实现均以本轮静态源码复核为依据,工程身份信息已脱敏。

先说明事实边界:当前版本已经能在挑战结束后筛出错题、恢复用户选项、标出正确答案、展示题目自带解析,并把错题加入本地错题本;但AnswerRecord只有题目 ID、所选选项和正误三个字段,没有错误类型、计算步骤、作答耗时或诊断置信度。下面先把现有链路讲清楚,再给出兼容旧数据的增强方案。

一、错题反馈不是一个弹窗,而是一条数据链路

一次挑战从答题到复盘,至少跨过五个边界:

  1. PracticePage接收选项并生成答题记录。
  2. 错题通过UserDataManager.addWrong()写入本地集合。
  3. 交卷时把records序列化成路由参数。
  4. ExamResultPage反序列化记录并筛选复盘入口。
  5. 再次进入PracticePage的wrongAnalysis模式,恢复错选项并展示解析。

真正需要守住的是“题目、用户答案、正确答案、解析内容”之间的一致性。如果路由参数损坏、题库版本变化,或者记录顺序与题目顺序不一致,页面即使没有崩溃,也可能把甲题的答案显示到乙题上。因此,错题反馈首先是数据契约问题,其次才是颜色与卡片样式问题。

二、现有 AnswerRecord 能回答什么

librarya/src/main/ets/models/MathModels.ets中的真实定义非常克制:

export interface AnswerRecord { questionId: string selected: string correct: boolean }

这三个字段可以回答:

  • 哪道题被作答;
  • 用户选择了哪个选项;
  • 这次选择是否正确。

它们无法回答:

  • 用户用了多长时间;
  • 用户中间写过哪些计算步骤;
  • 错误属于哪种类型;
  • 诊断来自规则还是用户自报;
  • 题目内容是否在记录之后发生变化。

因此,当前项目中的“错题解析”准确说是错题回放:根据questionId找回题目,恢复selected,再显示题目静态解析。文章后续给出的错误分类属于增强设计,不是对现有源码能力的虚构。

三、真实答题入口如何产生记录

PracticePage.selectOption()在首次选择后立即把页面切到解析态,并写入记录:

private selectOption(key: string): void { if (this.mode === 'wrongAnalysis') return if (this.showAnalysis) return this.selectedKey = key this.showAnalysis = true const q = this.currentQ() if (q) { const correct = key === q.answer this.records.push({ questionId: q.id, selected: key, correct }) if (!correct) { this.wrongRecords = UserDataManager.addWrong(this.wrongRecords, q.id, q.bankId) } else if (this.mode === 'wrong') { this.wrongRecords = UserDataManager.removeWrong(this.wrongRecords, q.id) } } }

这里有两个值得保留的细节。第一,showAnalysis是同步守卫,重复点击不会再次追加记录。第二,错题本与挑战记录分开保存:records服务于本次会话,wrongRecords服务于跨会话复习。

不过,records.push()直接修改数组,在复杂状态管理场景中不如不可变更新清晰。若后续增加诊断字段,可改为:

const record: AnswerRecordV2 = { schemaVersion: 2, questionId: q.id, selected: key, correct, answeredAt: Date.now(), elapsedMs: this.currentQuestionElapsedMs(), errorType: correct ? 'none' : this.inferErrorType(q, key) } this.records = [...this.records, record]

新对象一次性构造完成,便于日志、持久化、测试和未来迁移,也避免多个分支分别补字段。

四、错题本的去重语义

项目当前的本地错题写入不是简单追加。UserDataManager.addWrong()会先过滤同题旧记录,再把最新记录放到数组头部:

static addWrong( records: WrongRecord[], questionId: string, bankId: string ): WrongRecord[] { const filtered = records.filter(r => r.questionId !== questionId) const result: WrongRecord[] = [ { questionId, bankId, wrongAt: nowStr() }, ...filtered ] UserDataManager.persist(UserDataManager.K_WRONG, result) return result }

这意味着错题本当前采用“每题一条、最近错误时间优先”的集合语义,而不是完整错误历史。它适合回答“现在有哪些题需要复习”,不适合回答“同一题连续错了几次、错误类型是否发生变化”。

如果产品需要学习诊断,不应直接把WrongRecord膨胀成无限增长的历史数组。更稳妥的做法是保留两个模型:

模型用途建议主键
WrongRecord当前待复习错题questionId
AttemptRecord每次作答事件sessionId + questionId + attemptNo

一个负责快速展示,一个负责统计分析,避免“列表去重”和“历史留痕”互相冲突。

五、结果页如何把错题送入解析模式

挑战结束后,ExamResultPage从路由参数恢复答题记录。源码对 JSON 解析做了异常兜底:

try { this.records = JSON.parse(params.records) as AnswerRecord[] } catch (_) {}

点击“错题解析”时,结果页把整组记录再次序列化,跳转到答题页:

router.pushUrl({ url: 'pages/PracticePage', params: { bankId: this.bankId, mode: 'wrongAnalysis', records: JSON.stringify(this.records) } })

这一实现能跑通主流程,但as AnswerRecord[]只是编译期断言,不会验证运行时数据。字符串即使解析成功,也可能缺少questionId,或把correct写成字符串。面向真实设备做稳定性收口时,应把路由输入视为不可信边界。

可以增加一个窄化函数:

function isAnswerRecord(value: object): boolean { const data = value as Record<string, Object> return typeof data.questionId === 'string' && typeof data.selected === 'string' && typeof data.correct === 'boolean' } function parseAnswerRecords(raw: string): AnswerRecord[] { try { const parsed: Object = JSON.parse(raw) as Object if (!Array.isArray(parsed)) return [] return parsed.filter((item: Object) => isAnswerRecord(item)) .map((item: Object) => item as AnswerRecord) } catch (_) { return [] } }

实际项目需要按 ArkTS 编译器允许的对象类型进一步调整,但设计原则不变:解析、校验、降级放在路由边界,页面只消费已验证模型。

六、wrongAnalysis 为什么要按 questionId 对齐

loadWrongAnalysis()先过滤错误记录,再遍历记录并从题库查找题目:

this.records = examRecords.filter( (record: AnswerRecord) => !record.correct ) const all = getQuestions(params.bankId) const wrongQs: Question[] = [] for (const record of this.records) { const found = all.find(q => q.id === record.questionId) if (found) { wrongQs.push(found) } } this.questions = wrongQs

随后applyAnalysisState()用相同索引读取记录与题目:

private applyAnalysisState(): void { const record = this.records[this.currentIdx] this.selectedKey = record ? record.selected : '' this.showAnalysis = this.questions.length > 0 }

这里存在一个真实边界:如果某条记录对应的题目已经从题库删除,wrongQs会少一项,但records仍保留原长度,两个数组可能错位。更稳妥的结构不是维护平行数组,而是构造绑定对象:

interface WrongAnalysisItem { question: Question record: AnswerRecord } private buildWrongItems( records: AnswerRecord[], allQuestions: Question[] ): WrongAnalysisItem[] { const result: WrongAnalysisItem[] = [] for (const record of records) { if (record.correct) continue const question = allQuestions.find( (item: Question) => item.id === record.questionId ) if (question) { result.push({ question, record }) } } return result }

页面只遍历WrongAnalysisItem[],题目与作答天然成对,题库升级造成的缺失项也不会污染后续索引。

七、当前“计算步骤”实际上是什么

题目模型含有analysis和可选的example:

export interface Question { id: string bankId: string chapterId: string type: string stem: string options: Option[] answer: string analysis: string example?: string }

MockBanks.ets生成的解析是按题型准备的一句策略,例如:

  • 加法:“从个位算起,注意进位”;
  • 混合运算:“先算乘除,再算加减”;
  • 应用题:“先找数量关系”;
  • 限时题:“看清符号,按顺序计算”。

页面显示的是:

Text(this.currentQ()!.analysis) if (this.currentQ()!.example) { Text(`例句:${this.currentQ()!.example!}`) }

所以当前能力是“规则提示 + 完整算式示例”,不是用户实际计算轨迹,也不是逐步推导。若文章或商店文案声称“记录每一步计算”,就会超过源码事实。

八、把静态解析升级为结构化步骤

要让解析真正支持步骤展示,可以在题目生成阶段产生结构化数据,而不是临时切割中文字符串:

export interface CalculationStep { order: number expression: string result: string reason: string } export interface QuestionExplanation { strategy: string steps: CalculationStep[] checkpoint: string }

例如8 × 6 + 5可以生成:

const explanation: QuestionExplanation = { strategy: '混合运算先乘除、后加减', steps: [ { order: 1, expression: '8 × 6', result: '48', reason: '乘法优先' }, { order: 2, expression: '48 + 5', result: '53', reason: '再完成加法' } ], checkpoint: '先圈出乘号,再开始计算' }

结构化步骤的价值不只是 UI 好看。它允许无障碍朗读逐步播报、允许平板端用左右栏展示、允许测试直接断言步骤顺序,也方便未来根据错误类型突出某一步。

九、错误类型必须可解释

错误分类不能只凭“选错了哪个选项”就下结论。当前干扰项由correctOption()根据偏移量生成,选项并没有携带“为什么错”的语义。因此,直接把 B 解释为“进位错误”是不可靠的。

可采用两层分类:

export type ErrorType = 'none' | 'operator_misread' | 'order_of_operations' | 'carry_or_borrow' | 'fact_recall' | 'word_relation' | 'careless' | 'unknown' export interface ErrorDiagnosis { type: ErrorType source: 'rule' | 'self_report' confidence: number message: string }

规则层只在证据充分时分类。例如混合运算题中,用户答案恰好等于从左到右错误计算的结果,可以判断为order_of_operations;其他情况保持unknown。用户也可以在复盘时选择“看错符号”“不会算”“点错了”,以self_report保存。

错误分类的原则是:宁可显示“暂未判断”,也不要给孩子一个没有依据的标签。

十、兼容旧记录的 AnswerRecordV2

线上或已上架应用升级模型时,不能假设本地只有新数据。建议显式设置版本:

export interface AnswerRecordV2 { schemaVersion: 2 sessionId: string questionId: string selected: string correct: boolean answeredAt: number elapsedMs: number errorType: ErrorType } function migrateAnswerRecord(old: AnswerRecord): AnswerRecordV2 { return { schemaVersion: 2, sessionId: '', questionId: old.questionId, selected: old.selected, correct: old.correct, answeredAt: 0, elapsedMs: 0, errorType: old.correct ? 'none' : 'unknown' } }

旧数据缺失的信息用明确的未知值表示,不能根据当前时间或题型随意补造。迁移后仍能显示原有答案解析,新功能只对新作答提供更丰富诊断。

十一、反馈卡片如何组织信息层级

错题反馈页不应一次把所有信息堆在屏幕上。推荐由浅入深:

  1. 第一层显示题干、用户选择、正确答案;
  2. 第二层显示结构化计算步骤;
  3. 第三层显示错误类型与下一次检查点;
  4. 最后提供“再做一道同类题”或“加入错题训练”。

对于当前源码,第一层与部分第二层已经具备:选项颜色区分正确和错误,解析卡显示正确答案、analysis与example。错误类型标签和逐步推导则需要新增模型和渲染组件。

在 ArkUI 中,步骤列表应放入可滚动区域,并为长文本设置layoutWeight、maxLines或自然换行。底部操作区继续使用项目现有的安全区计算,避免被手势导航区域遮挡。

十二、结果页还存在两个可复核风险

1. 历史记录可能重复写入

ExamResultPage.aboutToAppear()每次出现都会执行addExamHistory()。如果页面因生命周期或路由行为再次出现,而历史方法没有会话幂等键,就可能重复保存同一场挑战。增强模型中的sessionId可以同时解决答题记录关联与结果历史去重。

2. 排名是固定展示值

结果页当前显示${236}/${1258},源码没有联网排名或本地排名计算。它是静态界面内容,不应在文章中描述为真实平台排名。错题反馈设计也不应依赖这个数字。

这两个点与错误分类一样,都要坚持“显示内容必须有数据来源”。

十三、建议的服务边界

当诊断规则增多时,不要把所有逻辑塞回PracticePage。可以按职责拆分:

export class WrongFeedbackService { static buildItem( question: Question, record: AnswerRecordV2 ): WrongFeedbackItem { return { question, record, explanation: ExplanationFactory.build(question), diagnosis: ErrorDiagnosisService.diagnose(question, record) } } }

页面负责:

  • 接收已验证的复盘项;
  • 渲染选项、步骤和诊断;
  • 响应上一题、下一题与再次练习。

服务负责:

  • 生成计算步骤;
  • 根据题型和答案推断错误;
  • 输出稳定的展示模型。

仓储或UserDataManager负责:

  • 保存待复习错题;
  • 保存作答事件;
  • 执行模型迁移与清理策略。

这样可以在不启动完整 ArkUI 页面的情况下测试分类规则。

十四、验证用例不能只测“答错会变红”

至少覆盖以下场景:

场景预期
全部答对错题解析列表为空,并有明确空态
一道错题恢复用户选项,正确答案与题目匹配
未答题结果网格显示未答,不伪造错误类型
路由 JSON 损坏安全降级为空记录,不崩溃
记录缺少题目跳过缺失项,后续题目不发生索引错位
旧版记录迁移为unknown,原解析仍可查看
混合运算典型错误证据充分时标记运算顺序错误
无法判断的干扰项显示未知,不强行归因
重复进入结果页同一sessionId不重复写历史
深浅色与大字体正误颜色之外仍有文字和图标区分

尤其要注意无障碍:不能只靠红绿颜色表达正误。选项旁应同时显示“你的答案”“正确答案”或对应图标,保证色觉差异用户也能理解。

十五、排障时优先看数据,而不是先改 UI

现象常见原因检查方式
错题数量正确但题目错位records与questions平行数组失配打印 questionId 配对结果
用户答案没有恢复记录被过滤或 selected 为空检查路由解析与applyAnalysisState()
正确答案颜色不对option key 与 answer key 不一致核对A/B/C/D映射
解析内容太泛题库只有静态analysis增加结构化 explanation
错误类型总是 unknown干扰项没有错误语义在题目生成时携带 distractor reason
错题本重复主键和去重规则不明确核对questionId去重
历史记录重复结果页多次出现且无 sessionId为挑战建立幂等会话键

十六、发布前的工程检查清单

  • AnswerRecord的现有字段与文章描述一致;
  • wrongAnalysis只展示错误记录;
  • 题目与记录通过questionId绑定;
  • JSON 解析失败有空态或提示;
  • 题库缺题不会造成后续索引错位;
  • analysis和example被描述为静态解析,不冒充真实书写轨迹;
  • 错误类型有证据来源和未知兜底;
  • 旧记录迁移不伪造时间、步骤或错误原因;
  • 错题本集合与作答历史分开建模;
  • 红绿状态同时配有文字含义;
  • 结果页历史写入具备会话幂等性;
  • 平板、小窗、深浅色与底部安全区均完成验证。

十七、wrongAnalysis 与 wrong 是两条不同的数据路径

源码里同时存在wrongAnalysis和wrong两种模式,它们不能混为一个“错题功能”。wrongAnalysis接收刚结束挑战的完整records,因此能够恢复本次错选项;wrong则遍历持久化的WrongRecord,按题目 ID 重新组题。后者只知道哪道题待复习,不保存当时选了什么。

模式数据来源能够回答当前限制
wrongAnalysis结果页路由中的 AnswerRecord[]本次错题、用户选择、正确答案依赖路由数据和当前题库仍能匹配
wrongPreferences 中的 WrongRecord[]当前有哪些题需要重练不能恢复上次错误选项与错误原因

另外,selectOption()在wrongAnalysis模式会直接返回,这使复盘页保持只读;在wrong模式重新答对时,则会调用removeWrong()将该题移出错题本。设计新字段时应保留这两个不同目的,避免复盘操作意外改写待复习集合。

十八、题目与作答绑定后还要报告缺失项

WrongAnalysisItem能消除平行数组错位,但简单跳过找不到的题目仍可能让用户误以为本次只有更少的错题。组装服务应同时返回可展示项与缺失记录,页面才能区分“没有错题”和“有错题但题库版本已经无法恢复”。

interface WrongAnalysisResult { items: WrongAnalysisItem[] missingQuestionIds: string[] } function buildWrongAnalysis( records: AnswerRecord[], questions: Question[] ): WrongAnalysisResult { const items: WrongAnalysisItem[] = [] const missingQuestionIds: string[] = [] for (const record of records) { if (record.correct) continue const question = questions.find((item: Question) => item.id === record.questionId) if (question) { items.push({ question, record }) } else { missingQuestionIds.push(record.questionId) } } return { items, missingQuestionIds } }

展示层不需要泄露内部 ID,可以提示“本次有 1 道错题因题库更新无法恢复”。如果缺失比例很高,应提供返回结果页或重新挑战的出口,而不是进入一个看似正常、内容却不完整的解析列表。

十九、错误类型应建立证据等级

当前correctOption()只是围绕答案做数值偏移,干扰项没有携带“进位错误”“运算顺序错误”等语义。要记录错误类型,最重要的不是增加枚举,而是说明分类证据来自哪里。建议把诊断分为确定规则、候选规则、用户自报和未知四级。

证据可记录结果页面表达
错误答案精确命中某条可复算规则规则类型 + 规则版本“可能忽略了乘除优先”
多个规则都能得到同一错误答案候选类型集合请用户选择更符合的原因
用户主动选择“点错了”self_report明确标注来自用户反馈
没有可验证证据unknown“暂未判断错误原因”

文案最好使用“可能”“根据本次答案推测”,而不是给出能力诊断。对于儿童学习场景,错误类型应描述可改变的操作步骤,不使用“粗心”“能力差”等人格化标签;即使保留careless内部枚举,也应让界面呈现为“请再次核对符号和选项”。

二十、计算步骤必须由运算模型生成

题目当前的analysis是一句题型策略,example是完整算式和最终答案。它们适合做即时提示,却无法告诉页面哪一步应高亮、哪一步与错误选项相关。结构化步骤应来自生成题目时使用的同一运算树,而不是发布时对中文解析做字符串切割。

interface ArithmeticNode { operator: '+' | '-' | '×' | '÷' left: number | ArithmeticNode right: number | ArithmeticNode } interface CalculationTrace { questionId: string steps: CalculationStep[] finalAnswer: number ruleVersion: number }

以混合运算为例,先遍历运算树得到合法顺序,再输出每步表达式、结果和依据。测试可以用最终答案反向核对最后一步,也能验证所有中间结果都来自前一步。题库升级时用ruleVersion标记规则,避免旧记录套用新解释后出现前后矛盾。

二十一、持久化失败不能被成功状态掩盖

UserDataManager.persist()当前在putSync()或flushSync()出错时直接捕获异常,调用方仍会拿到已经更新的内存数组。于是页面可能立即显示“已加入错题本”,重启应用后记录却消失。这个风险来自静态代码路径,本轮没有制造磁盘故障进行复现。

export interface SaveResult<T> { success: boolean value: T message: string } static saveWrongRecords(records: WrongRecord[]): SaveResult<WrongRecord[]> { try { UserDataManager.persistWrong(records) return { success: true, value: records, message: '' } } catch (error) { return { success: false, value: records, message: '错题保存失败,请重试' } } }

实际改造可以用返回结果或抛出可识别异常,但 UI 必须知道持久化是否成功。失败时保留本页答题记录,允许用户继续查看解析,同时不要用已保存图标误导。验收应覆盖 Preferences 未初始化、写入失败和重新启动回读三种情况。

二十二、记录升级要同时设计容量与清理

如果新增AttemptRecord保存每次答案、用时和错误类型,数据量会从“每题最多一条错题”变成“每次作答一条事件”。只加字段不设保留策略,长期使用后 Preferences 中的 JSON 会持续增大,启动时解析和整体重写成本也会上升。

可以按产品目的选择策略:保留最近若干会话用于结果回放;为长期统计保留按日聚合;对已经掌握的错题只移出待复习集合,不一定删除历史事件;清空学习数据时同时清理记录、聚合与版本元数据。若记录规模需要查询、筛选和迁移,结构化数据库比单个 Preferences 数组更合适。

旧WrongRecord和AnswerRecord没有错误原因,迁移时必须写成unknown。不能根据当前题型、当前解析或迁移当天时间补造历史事实,否则统计图看似完整,证据却是假的。

二十三、端到端验收要覆盖两个入口和三类状态

错题反馈至少包含“本次结果复盘”和“错题本重练”两个入口,还要分别覆盖正常、部分恢复和空状态。最小烟测可以从一场包含正确题、错题和未答题的挑战开始,记录 questionId、selected 与预期题干,然后逐条核对结果页和复盘页。

  1. 完成挑战并进入结果页,确认错题数量来自correct=false的记录;
  2. 打开错题解析,核对题目、用户选择、正确答案、analysis 与 example;
  3. 删除或替换一条本地题目后再次恢复,确认缺失项不会导致后续索引错位;
  4. 传入损坏 JSON、空数组和字段类型错误,确认页面进入可理解的降级状态;
  5. 从错题本进入 wrong 模式,答对后确认该题被移出待复习集合;
  6. 重启应用,核对错题本与页面提示是否和持久化结果一致;
  7. 在深浅色、大字体、小窗与底部手势区检查文字、图标和操作可达性。

本轮只完成静态源码复核,未执行上述构建和真机步骤。因此文章把这些内容保留为验收方法,不写成已通过结论。

二十四、总结

口算王当前的错题链路已经具备一个可工作的闭环:答题页记录正误,错误题目进入本地错题本,结果页把答题记录传给wrongAnalysis,复盘页恢复用户选项并展示正确答案、题型解析和算式示例。

但它还不是完整的学习诊断系统。真实源码没有错误类型字段,也没有用户逐步计算轨迹。要进一步实现“显示计算步骤并记录错误类型”,应先把题目与记录绑定成单一复盘模型,再引入结构化步骤、可解释错误分类、版本迁移和sessionId幂等键。这样得到的反馈不仅告诉用户“错了”,还能够诚实地说明“系统知道什么、为什么这样判断,以及下一次应该检查哪一步”。


CSDN-SERIES:ALL-163187036

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/27 1:34:45

VMware Workstation Pro 完整卸载详细图文教程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/27 1:29:16

【芳心科技】F. 养老院电疗仪设计

实物效果图&#xff1a;实现功能&#xff1a;1. 采用 STM32 单片机作为控制核心。 2. 采用 MOSFET 开关管控制电极片的频率。 3. 通过电开关改变电极片的振幅。 4. 通过三极管改变电极片的电流。 5. 采用 LCD 显示屏进行显示。 6. 按键设置频率、振幅和电流数值。原理图&#x…

作者头像 李华
网站建设 2026/10/27 1:27:39

基于Springboot面向智慧教育的实习实践系统【附源码+文档】

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/27 1:26:50

基于Springboot高校科研信息管理系统【附源码+文档】

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/27 1:25:39

基于Springboot大学生职业规划系统【附源码+文档】

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华