[{"data":1,"prerenderedAt":658},["ShallowReactive",2],{"notes-buffett-sidebar-layout":3},{"id":4,"title":5,"body":6,"category":646,"date":647,"description":648,"extension":649,"meta":650,"navigation":238,"order":646,"path":651,"seo":652,"seoDescription":653,"seoTitle":654,"slug":655,"stem":656,"__hash__":657},"notes\u002Fnotes\u002F2026-05-06-buffett-sidebar-layout.md","巴菲特模块侧边栏布局重构笔记",{"type":7,"value":8,"toc":629},"minimark",[9,16,20,28,31,34,38,48,51,115,118,126,133,146,152,315,323,328,344,353,384,392,400,434,453,460,539,543,548,561,574,577,625],[10,11,12],"blockquote",{},[13,14,15],"p",{},"记录时间：2026-05-06",[17,18,19],"h2",{"id":19},"背景",[13,21,22,23,27],{},"巴菲特模块有 284 篇文章（致股东信 100 篇、访谈与文章 151 篇、股东大会 33 篇），原本全部以卡片网格平铺在 ",[24,25,26],"code",{},"\u002Fbuffett"," 页面上，页面极长，浏览体验差。",[13,29,30],{},"目标：改为类似文档站的双栏布局——左侧固定侧边栏显示分类目录，右侧显示文章详情，两侧独立滚动。",[17,32,33],{"id":33},"实现方案",[35,36,37],"h3",{"id":37},"布局结构",[39,40,45],"pre",{"className":41,"code":43,"language":44},[42],"language-text","┌──────────────────────────────────────────┐\n│  Header (sticky)                         │\n├──────────┬───────────────────────────────┤\n│ 侧边栏    │  文章内容                      │\n│ (260px)  │  (overflow-y: auto)           │\n│          │                               │\n│ ▼ 致股东信 │  标题 \u002F 描述                   │\n│   1956…  │  ─────────                    │\n│   1957…  │  正文 (ContentRenderer)        │\n│          │                               │\n│ ▶ 访谈    │                               │\n│ ▶ 股东大会 │                               │\n│          │                               │\n│ (独立滚动) │  (独立滚动)                    │\n└──────────┴───────────────────────────────┘\n","text",[24,46,43],{"__ignoreMap":47},"",[35,49,50],{"id":50},"关键文件",[52,53,54,67],"table",{},[55,56,57],"thead",{},[58,59,60,64],"tr",{},[61,62,63],"th",{},"文件",[61,65,66],{},"作用",[68,69,70,81,91,101],"tbody",{},[58,71,72,78],{},[73,74,75],"td",{},[24,76,77],{},"app\u002Fcomponents\u002FBuffettSidebar.vue",[73,79,80],{},"新建，侧边栏组件",[58,82,83,88],{},[73,84,85],{},[24,86,87],{},"app\u002Fpages\u002Fbuffett\u002F[slug].vue",[73,89,90],{},"重构为双栏布局",[58,92,93,98],{},[73,94,95],{},[24,96,97],{},"app\u002Fpages\u002Fbuffett\u002Findex.vue",[73,99,100],{},"改为重定向到第一篇文章",[58,102,103,108],{},[73,104,105],{},[24,106,107],{},"app\u002Fassets\u002Fcss\u002Fmain.css",[73,109,110,111,114],{},"新增 ",[24,112,113],{},"--header-h"," 变量",[17,116,117],{"id":117},"踩过的三个坑",[35,119,121,122,125],{"id":120},"坑-1position-sticky-劫持滚动","坑 1：",[24,123,124],{},"position: sticky"," 劫持滚动",[13,127,128,132],{},[129,130,131],"strong",{},"现象："," 侧边栏内容超出屏幕后无法滚动，鼠标滚轮只滚动右侧文章区。",[13,134,135,138,139,141,142,145],{},[129,136,137],{},"原因："," 最初侧边栏使用 ",[24,140,124],{},"，它仍然处于页面的滚动流中。浏览器滚动事件优先作用于页面（即右侧内容区），而不是侧边栏内部的 ",[24,143,144],{},"overflow-y: auto"," 子元素。",[13,147,148,151],{},[129,149,150],{},"修复："," 放弃 sticky 方案，改为双面板独立滚动模型：",[39,153,157],{"className":154,"code":155,"language":156,"meta":47,"style":47},"language-css shiki shiki-themes github-light github-dark",".buffett-layout {\n  height: calc(100vh - var(--header-h));\n  overflow: hidden;  \u002F* 父容器不滚动 *\u002F\n}\n\n.buffett-content {\n  overflow-y: auto;  \u002F* 右侧独立滚动 *\u002F\n}\n\n.sidebar-nav {\n  flex: 1;\n  overflow-y: auto;  \u002F* 左侧独立滚动 *\u002F\n}\n","css",[24,158,159,172,209,227,233,240,248,264,269,274,282,296,310],{"__ignoreMap":47},[160,161,164,168],"span",{"class":162,"line":163},"line",1,[160,165,167],{"class":166},"sScJk",".buffett-layout",[160,169,171],{"class":170},"sVt8B"," {\n",[160,173,175,179,182,185,188,191,195,198,201,203,206],{"class":162,"line":174},2,[160,176,178],{"class":177},"sj4cs","  height",[160,180,181],{"class":170},": ",[160,183,184],{"class":177},"calc",[160,186,187],{"class":170},"(",[160,189,190],{"class":177},"100",[160,192,194],{"class":193},"szBVR","vh",[160,196,197],{"class":193}," -",[160,199,200],{"class":177}," var",[160,202,187],{"class":170},[160,204,113],{"class":205},"s4XuR",[160,207,208],{"class":170},"));\n",[160,210,212,215,217,220,223],{"class":162,"line":211},3,[160,213,214],{"class":177},"  overflow",[160,216,181],{"class":170},[160,218,219],{"class":177},"hidden",[160,221,222],{"class":170},";  ",[160,224,226],{"class":225},"sJ8bj","\u002F* 父容器不滚动 *\u002F\n",[160,228,230],{"class":162,"line":229},4,[160,231,232],{"class":170},"}\n",[160,234,236],{"class":162,"line":235},5,[160,237,239],{"emptyLinePlaceholder":238},true,"\n",[160,241,243,246],{"class":162,"line":242},6,[160,244,245],{"class":166},".buffett-content",[160,247,171],{"class":170},[160,249,251,254,256,259,261],{"class":162,"line":250},7,[160,252,253],{"class":177},"  overflow-y",[160,255,181],{"class":170},[160,257,258],{"class":177},"auto",[160,260,222],{"class":170},[160,262,263],{"class":225},"\u002F* 右侧独立滚动 *\u002F\n",[160,265,267],{"class":162,"line":266},8,[160,268,232],{"class":170},[160,270,272],{"class":162,"line":271},9,[160,273,239],{"emptyLinePlaceholder":238},[160,275,277,280],{"class":162,"line":276},10,[160,278,279],{"class":166},".sidebar-nav",[160,281,171],{"class":170},[160,283,285,288,290,293],{"class":162,"line":284},11,[160,286,287],{"class":177},"  flex",[160,289,181],{"class":170},[160,291,292],{"class":177},"1",[160,294,295],{"class":170},";\n",[160,297,299,301,303,305,307],{"class":162,"line":298},12,[160,300,253],{"class":177},[160,302,181],{"class":170},[160,304,258],{"class":177},[160,306,222],{"class":170},[160,308,309],{"class":225},"\u002F* 左侧独立滚动 *\u002F\n",[160,311,313],{"class":162,"line":312},13,[160,314,232],{"class":170},[35,316,318,319,322],{"id":317},"坑-2grid-子项-min-height-auto-撑破容器","坑 2：Grid 子项 ",[24,320,321],{},"min-height: auto"," 撑破容器",[13,324,325,327],{},[129,326,131],{}," 改为双面板后侧边栏依然不滚动。",[13,329,330,332,333,336,337,339,340,343],{},[129,331,137],{}," CSS Grid 子项的默认 ",[24,334,335],{},"min-height"," 是 ",[24,338,258],{},"（由内容撑开），即使父容器有固定高度，子项也会被内容撑出去，",[24,341,342],{},"overflow"," 无法生效。",[13,345,346,348,349,352],{},[129,347,150],{}," 给 grid 子项加 ",[24,350,351],{},"min-height: 0","：",[39,354,356],{"className":154,"code":355,"language":156,"meta":47,"style":47},".desktop-sidebar {\n  min-height: 0;  \u002F* 允许 Grid 子项被约束 *\u002F\n}\n",[24,357,358,365,380],{"__ignoreMap":47},[160,359,360,363],{"class":162,"line":163},[160,361,362],{"class":166},".desktop-sidebar",[160,364,171],{"class":170},[160,366,367,370,372,375,377],{"class":162,"line":174},[160,368,369],{"class":177},"  min-height",[160,371,181],{"class":170},[160,373,374],{"class":177},"0",[160,376,222],{"class":170},[160,378,379],{"class":225},"\u002F* 允许 Grid 子项被约束 *\u002F\n",[160,381,382],{"class":162,"line":211},[160,383,232],{"class":170},[35,385,387,388,391],{"id":386},"坑-3vue-scoped-css-display-属性覆盖","坑 3：Vue scoped CSS ",[24,389,390],{},"display"," 属性覆盖",[13,393,394,396,397,399],{},[129,395,131],{}," 加了 ",[24,398,351],{}," 后还是不滚动。移动端抽屉正常，桌面端不行。",[13,401,402,404,405,408,409,411,412,415,416,419,420,411,423,426,427,430,431,433],{},[129,403,137],{}," 父组件 ",[24,406,407],{},"[slug].vue"," 的 scoped CSS 给 ",[24,410,362],{}," 设了 ",[24,413,414],{},"display: block","，子组件 ",[24,417,418],{},"BuffettSidebar.vue"," 给 ",[24,421,422],{},".sidebar",[24,424,425],{},"display: flex","。两者同级优先级（都是一个 class + 一个 ",[24,428,429],{},"[data-v-xxx]"," 属性选择器），源码加载顺序决定了 ",[24,432,414],{}," 胜出。",[13,435,436,438,439,442,443,446,447,449,450,452],{},[24,437,414],{}," 下，",[24,440,441],{},"flex: 1"," 和 ",[24,444,445],{},"flex-direction: column"," 全部失效，",[24,448,279],{}," 没有被约束高度，",[24,451,144],{}," 无法触发滚动。",[13,454,455,457,458,352],{},[129,456,150],{}," 移除父组件中多余的 ",[24,459,414],{},[39,461,463],{"className":154,"code":462,"language":156,"meta":47,"style":47},"\u002F* 之前 *\u002F\n.desktop-sidebar {\n  display: block;    \u002F* 覆盖了子组件的 display: flex *\u002F\n  min-height: 0;\n}\n\n\u002F* 修复后 *\u002F\n.desktop-sidebar {\n  min-height: 0;     \u002F* 只保留必要属性 *\u002F\n}\n",[24,464,465,470,476,492,502,506,510,515,521,535],{"__ignoreMap":47},[160,466,467],{"class":162,"line":163},[160,468,469],{"class":225},"\u002F* 之前 *\u002F\n",[160,471,472,474],{"class":162,"line":174},[160,473,362],{"class":166},[160,475,171],{"class":170},[160,477,478,481,483,486,489],{"class":162,"line":211},[160,479,480],{"class":177},"  display",[160,482,181],{"class":170},[160,484,485],{"class":177},"block",[160,487,488],{"class":170},";    ",[160,490,491],{"class":225},"\u002F* 覆盖了子组件的 display: flex *\u002F\n",[160,493,494,496,498,500],{"class":162,"line":229},[160,495,369],{"class":177},[160,497,181],{"class":170},[160,499,374],{"class":177},[160,501,295],{"class":170},[160,503,504],{"class":162,"line":235},[160,505,232],{"class":170},[160,507,508],{"class":162,"line":242},[160,509,239],{"emptyLinePlaceholder":238},[160,511,512],{"class":162,"line":250},[160,513,514],{"class":225},"\u002F* 修复后 *\u002F\n",[160,516,517,519],{"class":162,"line":266},[160,518,362],{"class":166},[160,520,171],{"class":170},[160,522,523,525,527,529,532],{"class":162,"line":271},[160,524,369],{"class":177},[160,526,181],{"class":170},[160,528,374],{"class":177},[160,530,531],{"class":170},";     ",[160,533,534],{"class":225},"\u002F* 只保留必要属性 *\u002F\n",[160,536,537],{"class":162,"line":276},[160,538,232],{"class":170},[35,540,542],{"id":541},"额外分类折叠状态重置","额外：分类折叠状态重置",[13,544,545,547],{},[129,546,131],{}," 点击文章链接后，所有分类都自动展开。",[13,549,550,552,553,556,557,560],{},[129,551,137],{}," ",[24,554,555],{},"\u003Cdetails open>"," 是硬编码属性，NuxtLink 导航导致组件重新渲染，所有 ",[24,558,559],{},"open"," 重置。",[13,562,563,565,566,569,570,573],{},[129,564,150],{}," 用 Vue ref（",[24,567,568],{},"Set\u003Cstring>","）管理每个分类的展开状态，初始只展开当前文章所在分类，通过 ",[24,571,572],{},"@toggle"," 事件同步状态。",[17,575,576],{"id":576},"经验",[578,579,580,597,605,619],"ul",{},[581,582,583,586,587,589,590,593,594,596],"li",{},[129,584,585],{},"双栏独立滚动","不要用 ",[24,588,124],{},"，应该让父容器固定高度 + ",[24,591,592],{},"overflow: hidden","，两个子面板各自 ",[24,595,144],{},"。",[581,598,599,600,602,603,596],{},"Grid 布局中如果子项需要被约束高度（内部有 ",[24,601,342],{}," 滚动），必须给子项加 ",[24,604,351],{},[581,606,607,608,611,612,615,616,618],{},"Vue scoped CSS 中，父组件和子组件对同一元素设置同一属性时会产生优先级竞争。最佳实践：父组件只设置布局相关属性（",[24,609,610],{},"margin","、",[24,613,614],{},"grid-area","），不要覆盖子组件的 ",[24,617,390],{}," 等核心属性。",[581,620,621,622,624],{},"调试 CSS 滚动问题时，从最内层的滚动容器开始检查：它是否有固定高度？它的每一层祖先是否都正确约束了高度？任何一层\"漏掉\"都会导致 ",[24,623,342],{}," 失效。",[626,627,628],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":47,"searchDepth":174,"depth":174,"links":630},[631,632,636,645],{"id":19,"depth":174,"text":19},{"id":33,"depth":174,"text":33,"children":633},[634,635],{"id":37,"depth":211,"text":37},{"id":50,"depth":211,"text":50},{"id":117,"depth":174,"text":117,"children":637},[638,640,642,644],{"id":120,"depth":211,"text":639},"坑 1：position: sticky 劫持滚动",{"id":317,"depth":211,"text":641},"坑 2：Grid 子项 min-height: auto 撑破容器",{"id":386,"depth":211,"text":643},"坑 3：Vue scoped CSS display 属性覆盖",{"id":541,"depth":211,"text":542},{"id":576,"depth":174,"text":576},null,"2026-05-06 00:00:00 CST","记录巴菲特模块从卡片平铺改为侧边栏+文章详情双栏布局的过程，以及修复侧边栏无法滚动的三个 CSS 坑。","md",{},"\u002Fnotes\u002F2026-05-06-buffett-sidebar-layout",{"title":5,"description":648},"Nuxt 3 双栏布局中侧边栏滚动失效的三个 CSS 陷阱：sticky 滚动劫持、Grid min-height 默认值、Vue scoped CSS display 覆盖。","巴菲特模块侧边栏布局重构笔记｜个人笔记","buffett-sidebar-layout","notes\u002F2026-05-06-buffett-sidebar-layout","xTB8ZbYiFyx1gO5lKlLRw-Hdn2XsAgehVvvObcPnhtg",1778291659458]