Vue修改密码页面开发实战:表单校验、加载态与全局异常处理
1. 从改个密码说起为什么这个页面比想象中难做修改密码这四个字放在任何后台系统里都像是最不起眼的一个功能。产品经理画原型的时候可能只给了一个弹窗前端拿到需求的第一反应往往是半小时搞定。但真做过几个中后台项目的人都知道这个页面是典型的看着简单、坑全在细节里——它同时牵扯到表单校验、接口安全、全局异常处理、加载态管理、路由守卫这几条线任何一条没处理好用户就会遇到点了没反应报错看不懂改完密码还能用旧密码登录这类问题。这篇内容围绕一个基于 Element UI 的修改密码页面展开核心要解决三件事页面本身怎么设计才既安全又好用、加载态怎么管理才不闪烁不卡顿、异常怎么全局捕捉才能让用户看到人话而不是一串堆栈。适合正在做中后台系统的前端开发者尤其是用 Vue 2 Element UI 这套经典组合的团队。哪怕你现在用的是 Vue 3 Element Plus思路也是通的只是 API 名字换一换。我先把结论摆前面修改密码这个功能前端能做的安全防护其实很有限但体验优化和异常兜底的空间非常大。很多人把精力全花在密码强度正则上却忽略了加载态和异常处理结果就是用户改密码改得一肚子火。下面我按实际开发顺序把每个环节拆开讲。2. 修改密码页面的字段设计与校验逻辑2.1 三个输入框背后的真实需求一个标准的修改密码表单通常是三个字段原密码、新密码、确认新密码。看起来平平无奇但每个字段的设计都有讲究。原密码字段的作用不只是验证身份它还有一个隐藏价值防止用户在公共设备上被他人恶意改密。所以这个字段必须要求输入且不能做任何记住上次输入的处理autocomplete属性要显式设置为off或者new-password避免浏览器自动填充把旧密码暴露出来。新密码字段是校验的重灾区。常见的强度规则包括长度 8-20 位、必须包含大小写字母和数字、不能包含连续字符如 123456、abcdef、不能和原密码相同。这里有个经验规则不要一次性全堆给用户看而是在输入过程中动态提示。Element UI 的el-form配合自定义validator就能做到用户每输入一个字符就实时反馈哪条规则还没满足比提交后弹一堆红字友好得多。确认密码字段的核心是一致性校验但要注意一个细节当用户修改新密码后确认密码的校验状态需要重新触发。很多人只写了validator却忘了在newPassword变化时手动validateField(confirmPassword)导致用户改了新密码、确认框还显示通过提交时才报错。2.2 用自定义 validator 把校验写扎实Element UI 的el-form校验依赖async-validator内置规则能覆盖大部分场景但密码强度这种复合规则必须自己写。下面是一段我实际项目里用过的校验逻辑做了简化data() { const validateNewPassword (rule, value, callback) { if (!value) { return callback(new Error(请输入新密码)) } if (value this.form.oldPassword) { return callback(new Error(新密码不能与原密码相同)) } const hasLetter /[a-zA-Z]/.test(value) const hasNumber /\d/.test(value) if (!hasLetter || !hasNumber) { return callback(new Error(密码需同时包含字母和数字)) } if (/(.)\1{2,}/.test(value)) { return callback(new Error(密码不能包含三个及以上连续相同字符)) } callback() } const validateConfirm (rule, value, callback) { if (value ! this.form.newPassword) { return callback(new Error(两次输入的密码不一致)) } callback() } return { form: { oldPassword: , newPassword: , confirmPassword: }, rules: { oldPassword: [{ required: true, message: 请输入原密码, trigger: blur }], newPassword: [{ required: true, validator: validateNewPassword, trigger: blur }], confirmPassword: [{ required: true, validator: validateConfirm, trigger: blur }] } } }这里有个容易忽略的点trigger用blur还是change。密码强度校验建议用blur因为用户输入过程中频繁触发校验会让界面一直闪红字体验很差而确认密码的一致性校验可以用change让用户改完立刻知道对不对。同一个表单里不同字段用不同 trigger是提升体验的细节。2.3 密码可见性切换与输入框的交互细节Element UI 的el-input支持show-password属性一行代码就能加上小眼睛切换。但要注意show-password在旧版本里叫show-password新版本 Element Plus 里也是这个名字别写成showPassword或者password否则不生效。另外三个密码框建议都加上maxlength20防止用户粘贴超长字符串导致后端截断或报错。clearable属性看情况加原密码框不建议加避免用户误点清空后还得重新输。3. 加载态管理别让用户对着白屏发呆3.1 页面级加载与按钮级加载的区别加载页面这个需求很多人第一反应是加个v-loading就完事了。但实际场景里加载态分两个层级页面级加载进入修改密码页面时可能需要先拉取一些初始化数据比如用户的安全问题、密码最后修改时间。这时候用v-loading盖住整个表单区域是合理的。按钮级加载用户点击确认修改后请求发出到返回的这段时间应该只让提交按钮进入loading状态而不是整个页面转圈。因为整个页面转圈会让用户觉得系统卡死了而按钮转圈传达的是正在处理你的请求心理感受完全不同。Element UI 的el-button自带loading属性直接绑定一个布尔值即可el-button typeprimary :loadingsubmitting clickhandleSubmit {{ submitting ? 提交中... : 确认修改 }} /el-button注意loading状态下按钮会自动禁用点击不需要额外写:disabled否则可能出现样式冲突。3.2 防止重复提交的三重保险加载态最核心的作用其实是防止重复提交。用户网络慢的时候会疯狂点按钮如果不做处理可能发出五六个请求后端如果没做幂等密码就被改了多次甚至出现新密码是第几次提交的这种诡异问题。我的做法是三重保险按钮 loading视觉上禁用这是第一层。方法入口拦截在handleSubmit开头判断if (this.submitting) return这是第二层防止键盘回车等绕过按钮的操作。请求取消用 axios 的CancelToken或者 AbortController在组件销毁或新请求发起时取消上一个未完成的请求这是第三层。第三层很多人不做但在用户快速切换页面时特别有用。如果不取消请求返回后组件已经销毁回调里访问this可能报错或者触发一些莫名其妙的全局提示。3.3 加载态的最小展示时间有个反直觉的经验加载态不要一闪而过。如果接口响应特别快比如内网 50ms 就返回loading 图标闪一下反而让用户觉得是不是没点上。我一般会做一个最小展示时间比如 300ms用Promise.all配合一个延时 Promise 实现const minDelay new Promise(resolve setTimeout(resolve, 300)) const request axios.post(/api/change-password, this.form) await Promise.all([request, minDelay])这样无论接口多快loading 至少显示 300ms视觉上更稳定。当然这个值别设太大超过 500ms 用户就会觉得慢了。4. 全局异常捕捉让报错说人话4.1 为什么需要全局异常处理修改密码接口可能返回各种错误原密码错误、新密码不符合后端规则、账号被锁定、token 过期、服务器 500。如果每个错误都在catch里写一遍this.$message.error(err.message)代码会又臭又长而且不同开发者写的提示风格不统一有的显示请求失败有的显示Error: Request failed with status code 400用户看了完全懵。全局异常捕捉的价值就在于统一拦截、统一转换、统一展示。前端拿到错误后先判断错误类型再决定是弹全局提示、跳登录页还是交给业务代码自己处理。4.2 axios 拦截器里的错误分类处理我一般会在 axios 的响应拦截器里做第一层处理按 HTTP 状态码和业务码分类axios.interceptors.response.use( response { const res response.data if (res.code ! 0) { // 业务错误交给全局提示 handleBusinessError(res) return Promise.reject(res) } return res }, error { if (error.response) { const status error.response.status if (status 401) { // token 失效跳登录 store.dispatch(user/logout) router.push(/login) Message.error(登录已过期请重新登录) } else if (status 403) { Message.error(没有权限执行此操作) } else if (status 500) { Message.error(服务器开小差了请稍后重试) } else { Message.error(error.response.data.message || 请求失败) } } else if (error.code ECONNABORTED) { Message.error(请求超时请检查网络) } else { Message.error(网络异常请稍后重试) } return Promise.reject(error) } )这里的关键是区分能自动处理的错误和需要业务代码处理的错误。比如 401 直接跳登录业务代码不用管但原密码错误这种业务错误可能需要把焦点定位到原密码输入框这就得让业务代码自己处理。所以拦截器里处理完通用错误后仍然要reject让业务层的catch有机会介入。4.3 业务错误的提示策略修改密码接口最常见的业务错误就是原密码错误。这个提示不能只弹一个 toast因为用户很可能需要重新输入。我的做法是toast 提示 清空原密码框 聚焦。async handleSubmit() { if (this.submitting) return try { await this.$refs.form.validate() } catch (e) { return } this.submitting true try { await changePassword(this.form) this.$message.success(密码修改成功请重新登录) setTimeout(() { this.$router.push(/login) }, 1000) } catch (err) { if (err.code OLD_PASSWORD_WRONG) { this.form.oldPassword this.$refs.oldPasswordInput.focus() } } finally { this.submitting false } }注意finally里一定要把submitting置回 false否则一旦出错按钮就永远转圈了。这是新手最容易犯的错误之一。4.4 Vue 全局错误捕获兜底除了接口错误还有一类错误是代码运行时错误比如某个组件里访问了 undefined 的属性。这类错误如果不捕获整个页面可能白屏。Vue 提供了Vue.config.errorHandler做全局兜底Vue.config.errorHandler function (err, vm, info) { console.error(全局捕获:, err, info) // 上报到监控平台 reportError(err, info) // 给用户一个友好提示避免白屏 Message.error(页面出现异常请刷新重试) }这个兜底不是万能的它捕获不了 Promise 里未 catch 的错误所以还需要监听unhandledrejectionwindow.addEventListener(unhandledrejection, event { console.error(未处理的 Promise 错误:, event.reason) reportError(event.reason) })两层加起来基本能覆盖绝大多数异常场景。5. 接口安全与前端能做的防护边界5.1 前端加密到底有没有用很多项目要求密码传输前加密常见做法是用 RSA 或 AES 在前端加密后再发给后端。这里必须说清楚前端加密不能替代 HTTPS。如果没上 HTTPS中间人可以直接抓包拿到加密后的密文虽然不能直接还原明文但可以重放攻击。前端加密的真正价值是防止日志和监控系统记录明文密码。很多公司的接口日志会把请求体打出来如果不加密运维和开发就能在日志里看到用户密码这是合规大忌。所以前端加密更多是防内部泄露而不是防外部攻击。如果要做推荐用 RSA 公钥加密公钥由后端下发私钥只在后端保存。前端用jsencrypt这类库即可但要注意公钥的获取时机和缓存策略别每次请求都去拉公钥。5.2 修改成功后的强制下线密码修改成功后必须让当前 token 失效并强制重新登录。原因很简单如果旧 token 还能用那攻击者拿到旧 token 依然能操作改密码就失去了意义。实现上有两种方式一是后端在改密成功后主动使该用户所有 token 失效前端收到成功响应后跳登录页二是前端在成功后主动调登出接口。推荐第一种因为更安全前端只需要负责跳转。跳转前给用户一个明确的提示比如密码修改成功即将重新登录然后延迟 1-2 秒跳转让用户有时间看到提示。直接瞬间跳转会让用户困惑我刚才到底改成功没有。5.3 密码字段的自动填充与浏览器记忆浏览器对typepassword的输入框有自动填充和记住密码的行为这在修改密码场景里是负面的。除了设置autocompletenew-password还可以用一些技巧把原密码框的name属性设成一个不常见的值避免浏览器识别为登录表单。在表单外层包一个autocompleteoff的 form。如果还是被填充可以在mounted里延迟清空一次。这些手段不能 100% 阻止浏览器行为但能大幅降低误填充概率。6. 踩坑实录那些文档里不会写的细节6.1 el-form 校验的异步陷阱this.$refs.form.validate()返回的是一个 Promise但如果你传了回调函数它就不返回 Promise 了。很多人混用两种写法导致await拿不到结果// 错误写法传了回调await 拿到的是 undefined await this.$refs.form.validate((valid) { ... }) // 正确写法一不传回调用 try/catch try { await this.$refs.form.validate() } catch (e) { return } // 正确写法二传回调不用 await this.$refs.form.validate((valid) { if (valid) { this.submit() } })这个坑我见过至少五个同事踩过表现就是校验没通过但请求还是发出去了。6.2 loading 与路由跳转的时序问题改密成功后跳登录页如果跳转太快loading 状态还没重置登录页可能继承一些奇怪的全局状态。我的做法是先重置 loading再跳转并且用setTimeout给一个缓冲this.submitting false this.$message.success(修改成功) setTimeout(() this.$router.push(/login), 800)另外如果用了 keep-alive 缓存了这个页面跳转前记得把表单重置否则用户下次进来还看到上次的密码虽然密码框显示的是圆点但值还在。6.3 全局异常处理与业务提示的冲突有时候全局拦截器已经弹了一个错误提示业务代码的catch里又弹了一个用户就看到两个 toast 叠在一起。解决办法是在错误对象上打标记// 拦截器里 error.handled true Message.error(...) // 业务层 catch (err) { if (!err.handled) { Message.error(err.message) } }这样全局处理过的错误业务层就不再重复提示。这个模式在大型项目里几乎是标配。6.4 密码强度提示的实时性前面提到用blur触发校验但强度提示比如还需包含数字最好用input事件实时更新而不是等失焦。可以单独维护一个strengthTips数组在input时计算computed: { strengthTips() { const pwd this.form.newPassword return [ { text: 至少8位, ok: pwd.length 8 }, { text: 包含字母, ok: /[a-zA-Z]/.test(pwd) }, { text: 包含数字, ok: /\d/.test(pwd) } ] } }然后在模板里用不同颜色渲染用户输入时就能看到哪条变绿了体验比提交后报错好太多。7. 一套可复用的实现骨架把上面的内容串起来一个完整的修改密码页面大致包含这些部分表单结构、校验规则、加载态管理、提交逻辑、异常处理、成功后的跳转。我把它整理成一个对照表方便你按图索骥模块关键点常见坑表单字段原密码、新密码、确认密码忘记关闭自动填充校验规则自定义 validator、动态 trigger改新密码后未重校验确认框加载态按钮级 loading、最小展示时间出错后未重置 loading防重复提交入口拦截 请求取消只靠按钮禁用回车可绕过全局异常拦截器分类处理、错误标记全局与业务重复弹提示成功后处理强制下线、延迟跳转跳转过快、状态未重置这套骨架我在几个中后台项目里反复用过基本能覆盖 90% 的修改密码场景。剩下的 10% 是各家的特殊需求比如有的要求改密后发邮件通知有的要求二次验证那些就在这个骨架上加钩子即可。最后分享一个我自己的习惯修改密码页面写完一定要用慢网络模拟测一遍。Chrome DevTools 里把网络调到 Slow 3G然后疯狂点提交按钮看看会不会重复请求、loading 会不会卡死、错误提示会不会叠。这个测试花不了五分钟但能提前发现大部分加载态和异常处理的问题。比等到测试同学提 bug 再改成本低得多。

相关新闻

ORB-SLAM3 void Frame::UpdatePoseMatrices()

ORB-SLAM3 void Frame::UpdatePoseMatrices()

void Frame::UpdatePoseMatrices() {Sophus::SE3<float> Twc mTcw.inverse();mRwc Twc.rotationMatrix();mOw Twc.translation();mRcw mTcw.rotationMatrix();mtcw mTcw.translation(); }见代码知其意&#xff0c;在SetPose() 中被调用

2026/10/15 19:13:00 阅读更多 →
前端进阶两大支柱:框架底层机制与工程化性能体系

前端进阶两大支柱:框架底层机制与工程化性能体系

前端进阶这条路&#xff0c;我观察了很多人&#xff0c;也带了不少新人。有个现象挺有意思&#xff1a;有些前端工作两三年&#xff0c;API背得滚瓜烂熟&#xff0c;组件写得也溜&#xff0c;可一到线上出问题就抓瞎&#xff1b;另一类人能力未必多广&#xff0c;但给他一个复杂…

2026/10/15 19:13:00 阅读更多 →
2026年跨境电商还能做吗?这5个长尾赛道普通人刚跑通,有人已月入3万

2026年跨境电商还能做吗?这5个长尾赛道普通人刚跑通,有人已月入3万

跨境电商在2026年是否还值得普通人尝试&#xff1f;答案并非简单的能或不能&#xff0c;而是取决于是否选对了细分方向。过去那种靠铺货、跟卖、低价冲量的模式&#xff0c;对资金、供应链和运营能力的要求已经很高&#xff0c;普通人硬拼很难跑通。但一些长尾赛道因为需求分散…

2026/10/15 19:11:59 阅读更多 →

最新新闻

基于React Native鸿蒙的智能调度分配模块设计与实践

基于React Native鸿蒙的智能调度分配模块设计与实践

做服务调度类项目&#xff0c;最让人头大的从来不是页面能做得有多花哨&#xff0c;而是“这件事到底该派给谁、怎么快速判断那个人现在真能用”这一连串业务规则背后的逻辑。最近我负责了一个基于React Native鸿蒙跨平台实现的调度分配模块&#xff0c;核心需求很明确&#xf…

2026/10/15 22:10:40 阅读更多 →
临床小讲课高效备:用WorkBuddy快速生成主动脉夹层PPT

临床小讲课高效备:用WorkBuddy快速生成主动脉夹层PPT

1. 临时被点名做小讲课&#xff0c;为什么我第一时间想到的不是自己硬肝在临床科室待过的人都懂&#xff0c;主任临时点名让你第二天晨会做个小讲课&#xff0c;那种感觉比夜班收三个急诊还刺激。尤其是讲主动脉夹层这种心血管急危重症&#xff0c;内容深、进展快、指南更新频繁…

2026/10/15 22:10:40 阅读更多 →
PMP第8章项目质量管理实战:从规划到控制的全流程指南

PMP第8章项目质量管理实战:从规划到控制的全流程指南

1. 质量管理不是“质检员的事”&#xff1a;从三个真实翻车场景说起很多人考完PMP之后&#xff0c;对第8章的记忆就只剩下“规划质量管理、管理质量、控制质量”这三个过程组名称。真到了项目上&#xff0c;要么把质量管理等同于测试团队的事&#xff0c;要么觉得“质量”是个软…

2026/10/15 22:10:40 阅读更多 →
PAT甲级1104:从暴力到O(N)公式,贡献法破解所有连续子段求和与精度坑

PAT甲级1104:从暴力到O(N)公式,贡献法破解所有连续子段求和与精度坑

如果你刷过 PAT 甲级题库&#xff0c;一定见过这道“智力题”味的题目&#xff1a;1104 Sum of Number Segments (20分)。题面只有一句话&#xff0c;意思就是给一串正数&#xff0c;求所有连续子段各自的和&#xff0c;再把所有和加起来。听起来是个人都会做&#xff0c;三层循…

2026/10/15 22:10:40 阅读更多 →
万能BIOS刷新工具实战:SPI Flash刷写与校验救砖全攻略

万能BIOS刷新工具实战:SPI Flash刷写与校验救砖全攻略

简介&#xff1a;一款面向主板BIOS升级场景的刷新工具&#xff0c;适用于需要修复启动故障、提升硬件兼容性或更新固件版本的电脑用户与运维人员。工具宣称“万能”&#xff0c;实际兼容性仍需按主板型号核对&#xff0c;但覆盖常见主板刷新需求。资源包共4个文件&#xff0c;包…

2026/10/15 22:10:40 阅读更多 →
Unity Profiler性能分析实战:从真机采集到GC优化完整指南

Unity Profiler性能分析实战:从真机采集到GC优化完整指南

1. 为什么每个Unity开发者都该把Profiler当日常工具做Unity这几年&#xff0c;我见过太多项目在编辑器里跑得飞起&#xff0c;一打包到真机就卡成幻灯片。帧率从90掉到22&#xff0c;发热烫手&#xff0c;玩家骂声一片&#xff0c;团队却找不到瓶颈在哪。这种场景几乎每个中大型…

2026/10/15 22:09:39 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时&#xff0c;网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上&#xff0c;依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里&#xff0c;扩缩容到底难在哪做过大模型推理服务部署的人&#xff0c;大概都经历过这样的场景&#xff1a;一个团队共用一套推理集群&#xff0c;A 业务跑的是 7B 的小模型&#xff0c;B 业务跑的是 72B 的大模型&#xff0c;C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子&#xff1a;一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减&#xff0c;为了把启动时间压进两秒以内&#xff0c;我手贱地把一串自认为“没用”的 systemd 服务给 disable 了&#xff0c;其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介&#xff1a;基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码&#xff0c;面向计算机相关专业课程设计与期末大作业学生&#xff0c;以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程&#xff0c;…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程&#xff1a;键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化&#xff0c;十个新手有八个栽在"往输入框里填东西"这件事上&#xff1a;要么填不进去&#xff0c;要么填了一半&#xff0c;要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程&#xff1a;阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀&#xff1a;什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面&#xff0c;跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/15 9:49:14 阅读更多 →