@@ -49,15 +49,15 @@ sidebar: false
4949 transition : transform .15s ease , box-shadow .15s ease , border-color .15s ease ;
5050 display : flex ;
5151 flex-direction : column ;
52- height : 300 px ; /* 每个分类卡片固定高度 */
52+ /* 分栏模式下高度自适应 */
5353}
5454.hub-card :hover {
5555 transform : translateY (-1px );
5656 box-shadow : var (--el-box-shadow );
5757 border-color : var (--el-color-primary );
5858}
5959.hub-card h3 { margin : 0 0 8px ; font-size : 16px ; font-weight : 600 ; }
60- .hub-card .links { flex : 1 ; overflow-y : auto ; padding-right : 4px ; }
60+ .hub-card .links { flex : 1 ; padding-right : 4px ; }
6161.hub-card a {
6262 display : inline-block ;
6363 margin : 4px 8px 0 0 ;
@@ -69,6 +69,27 @@ sidebar: false
6969}
7070.hub-card a :hover { background : var (--el-color-primary ); color : #fff ; }
7171
72+ /* 分栏类型布局(列式导航) */
73+ .nav-columns {
74+ display : grid ;
75+ grid-template-columns : repeat (auto-fill , minmax (260px , 1fr ));
76+ gap : 14px ;
77+ margin-top : 16px ;
78+ }
79+ .col-block {
80+ padding : 14px ;
81+ border-radius : 12px ;
82+ background : var (--el-bg-color-overlay );
83+ box-shadow : var (--el-box-shadow-light );
84+ border : 1px solid var (--el-border-color-light );
85+ transition : transform .15s ease , box-shadow .15s ease , border-color .15s ease ;
86+ }
87+ .col-block :hover { transform : translateY (-1px ); box-shadow : var (--el-box-shadow ); border-color : var (--el-color-primary ); }
88+ .col-block h3 { margin : 0 0 8px ; font-size : 16px ; font-weight : 600 ; }
89+ .col-block .links { display : flex ; flex-wrap : wrap ; gap : 6px 8px ; }
90+ .col-block a { display : inline-block ; padding : 4px 8px ; border-radius : 8px ; background : var (--el-color-primary-light-9 ); color : var (--el-text-color-primary ); text-decoration : none ; }
91+ .col-block a :hover { background : var (--el-color-primary ); color : #fff ; }
92+
7293.articles { margin-top : 20px ; }
7394.articles details {
7495 margin : 10px 0 ;
@@ -92,11 +113,30 @@ sidebar: false
92113.articles li { margin : 0 ; }
93114.articles a { display : block ; padding : 6px 10px ; border-radius : 8px ; background : var (--el-color-primary-light-9 ); text-decoration : none ; color : var (--el-text-color-primary ); }
94115.articles a :hover { background : var (--el-color-primary ); color : #fff ; }
116+
117+ /* 顶部分栏切换控件 */
118+ .seg-switch { display : inline-flex ; border : 1px solid var (--el-border-color ); border-radius : 10px ; overflow : hidden ; background : var (--el-bg-color ); margin-top : 12px ; }
119+ .seg-item { padding : 6px 12px ; cursor : pointer ; color : var (--el-text-color-secondary ); user-select : none ; }
120+ .seg-item :hover { background : var (--el-color-primary-light-9 ); color : var (--el-color-primary ); }
121+ .seg-item.is-active { background : var (--el-color-primary ); color : #fff ; }
122+
123+ .columns3 { display : grid ; grid-template-columns : 220px 260px 1fr ; gap : 14px ; margin-top : 16px ; }
124+ .col { padding : 14px ; border-radius : 12px ; background : var (--el-bg-color-overlay ); box-shadow : var (--el-box-shadow-light ); border : 1px solid var (--el-border-color-light ); }
125+ .col-title { margin : 0 0 10px ; font-size : 16px ; font-weight : 600 ; }
126+ .list { display : flex ; flex-direction : column ; gap : 6px ; }
127+ .item { padding : 6px 10px ; border-radius : 8px ; background : var (--el-color-primary-light-9 ); color : var (--el-text-color-primary ); cursor : pointer ; }
128+ .list a .item { display : block ; }
129+ .item :hover { background : var (--el-color-primary ); color : #fff ; }
130+ .item.is-active { background : var (--el-color-primary ); color : #fff ; }
131+ .count { margin-left : 6px ; color : var (--el-text-color-secondary ); font-size : 12px ; }
132+ .col .links { display : flex ; flex-wrap : wrap ; gap : 6px 8px ; }
133+ .col .links a { display : inline-block ; padding : 4px 8px ; border-radius : 8px ; background : var (--el-color-primary-light-9 ); color : var (--el-text-color-primary ); text-decoration : none ; }
134+ .col .links a :hover { background : var (--el-color-primary ); color : #fff ; }
95135</style >
96136
97137<script setup >
98138import { useData } from ' vitepress'
99- import { computed } from ' vue'
139+ import { computed , ref } from ' vue'
100140const { site , theme } = useData ()
101141
102142// 自动收集 docs 下的所有 Markdown 页面
@@ -179,9 +219,67 @@ const navCards = computed(() => {
179219 return cards
180220})
181221
222+ const normalizeNode = (n ) => ({
223+ text: n? .text || ' ' ,
224+ link: n? .link ,
225+ items: Array .isArray (n? .items ) ? n .items .map (normalizeNode) : []
226+ })
227+
228+ const sidebarTree = computed (() => {
229+ const sb = (theme .value && theme .value .sidebar ) || {}
230+ const allow = Array .isArray (theme .value ? .blogNavInclude ) ? new Set (theme .value .blogNavInclude ) : null
231+ const exclude = Array .isArray (theme .value ? .blogNavExclude ) ? new Set (theme .value .blogNavExclude ) : null
232+ const groups = []
233+ for (const [path , arr ] of Object .entries (sb)) {
234+ if (path === ' /' ) continue
235+ if (allow && ! allow .has (path)) continue
236+ if (exclude && exclude .has (path)) continue
237+ const name = path .replace (/ ^ \/ / , ' ' ).replace (/ \/ $ / , ' ' ) || ' 其他'
238+ const nodes = Array .isArray (arr) ? arr .map (normalizeNode) : []
239+ groups .push ({ path, name, nodes })
240+ }
241+ return groups .sort ((a , b ) => a .name .localeCompare (b .name ))
242+ })
243+
244+ const activeIndices = ref ([0 ])
245+ const columns = computed (() => {
246+ const cols = []
247+ const groups = sidebarTree .value
248+ cols .push ({ title: ' 一级导航' , items: groups .map (g => ({ text: g .name , items: g .nodes })) })
249+ let nodes = groups[activeIndices .value [0 ]]? .nodes || []
250+ let level = 1
251+ while (nodes && nodes .length ) {
252+ cols .push ({ title: level === 1 ? ' 二级分类' : ` ${ level + 1 } 级` , items: nodes })
253+ const ai = activeIndices .value [level] ?? 0
254+ const chosen = nodes[ai]
255+ nodes = chosen? .items || []
256+ level++
257+ if (level > 8 ) break
258+ }
259+ return cols
260+ })
261+
262+ const onHover = (ci , ni ) => {
263+ const arr = activeIndices .value .slice (0 , ci)
264+ arr[ci] = ni
265+ activeIndices .value = arr
266+ }
267+
268+ const gridColsStyle = computed (() => {
269+ const n = columns .value .length
270+ const extra = Math .max (n - 3 , 0 )
271+ const repeat = extra ? ' 260px ' .repeat (extra) : ' '
272+ return { gridTemplateColumns: ` 220px 260px ${ repeat} 1fr` }
273+ })
274+
275+ const activeCol = ref (0 )
276+ const activeSection = ref (0 )
277+
182278// 侧边栏双/三层结构自动判断
183279const sidebarGroups = computed (() => {
184280 const sb = (theme .value && theme .value .sidebar ) || {}
281+ const allow = Array .isArray (theme .value ? .blogNavInclude ) ? new Set (theme .value .blogNavInclude ) : null
282+ const exclude = Array .isArray (theme .value ? .blogNavExclude ) ? new Set (theme .value .blogNavExclude ) : null
185283
186284 const collectLeaves = (node ) => {
187285 const res = []
@@ -213,7 +311,9 @@ const sidebarGroups = computed(() => {
213311 const name = path .replace (/ ^ \/ / , ' ' ).replace (/ \/ $ / , ' ' ) || ' 其他'
214312 groups .push ({ path, name, sections, items })
215313 }
216- return groups .sort ((a , b ) => a .name .localeCompare (b .name ))
314+ let filtered = allow ? groups .filter (g => allow .has (g .path )) : groups
315+ if (exclude && exclude .size ) filtered = filtered .filter (g => ! exclude .has (g .path ))
316+ return filtered .sort ((a , b ) => a .name .localeCompare (b .name ))
217317})
218318
219319const sidebarTotal = computed (() => sidebarGroups .value .reduce ((s , g ) => s + g .items .length + g .sections .reduce ((ss , sec ) => ss + sec .items .length , 0 ), 0 ))
@@ -234,11 +334,16 @@ const groupCount = (g) => g.items.length + g.sections.reduce((s, sec) => s + sec
234334
235335< / div>
236336
237- < div class = " nav-hub" v- if = " navCards.length" >
238- < div class = " hub-card" v- for = " card in navCards" : key= " card.title" >
239- < h3> {{ card .title }}< / h3>
240- < div class = " links" >
241- < a v- for = " lnk in card.links" : key= " lnk.link" : href= " lnk.link" > {{ lnk .text }}< / a>
337+ < div class = " columns3" : style= " gridColsStyle" >
338+ < div class = " col" v- for = " (col, ci) in columns" : key= " ci" >
339+ < h3 class = " col-title" > {{ col .title }}< / h3>
340+ < div class = " list" >
341+ < template v- for = " (node, ni) in col.items" : key= " (node.link || '') + node.text + ni" >
342+ < a v- if = " node.link && (!node.items || !node.items.length)" class = " item" : href= " node.link" > {{ node .text }}< / a>
343+ < div v- else class = " item" : class = " { 'is-active': (activeIndices[ci] ?? 0) === ni }" @mouseenter= " onHover(ci, ni)" >
344+ {{ node .text }}< span class = " count" v- if = " node.items && node.items.length" > ({{ node .items .length }})< / span>
345+ < / div>
346+ < / template>
242347 < / div>
243348 < / div>
244349< / div>
0 commit comments