Skip to content

Commit 3cb475c

Browse files
committed
feat: 更新
1 parent 5e6af7a commit 3cb475c

File tree

3 files changed

+134
-17
lines changed

3 files changed

+134
-17
lines changed

docs/.vitepress/config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,7 @@ export default defineConfig({
721721
// }
722722
// 会员相关(示例激活码,可在此处配置多个,支持明文或SHA-256哈希)
723723
vipActivationHashes: ["e5b1764ba2392383c2c97b5ea76f9978a590be0ef0811fdd0d7b53b0c0cd0ebc"],
724+
blogNavExclude: ["/nav/","/spider-tools/"],
724725
},
725726
define: {
726727
__API_BASE__: JSON.stringify('https://api.example.com'),

docs/navigation/index.md

Lines changed: 114 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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: 300px; /* 每个分类卡片固定高度 */
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>
98138
import { useData } from 'vitepress'
99-
import { computed } from 'vue'
139+
import { computed, ref } from 'vue'
100140
const { 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
// 侧边栏双/三层结构自动判断
183279
const 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

219319
const 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>

docs/vip/index.md

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,23 @@ sidebar: false
77

88
<script setup>
99
import { ref, onMounted } from 'vue'
10-
import alertify from 'alertifyjs'
1110
import { useData } from 'vitepress'
1211

13-
const showAccount = () => {
14-
alertify.success('加微:w021105x 备注:购买博客VIP')
12+
let alertifyRef = null
13+
const loadAlertify = async () => {
14+
if (alertifyRef) return alertifyRef
15+
if (typeof window === 'undefined') return null
16+
const mod = await import('alertifyjs')
17+
alertifyRef = mod.default || mod
18+
return alertifyRef
1519
}
20+
const toast = async (type, msg) => {
21+
const al = await loadAlertify()
22+
if (!al) return
23+
if (al[type]) al[type](msg)
24+
else al.success(msg)
25+
}
26+
const showAccount = () => { toast('success', '加微:w021105x 备注:开通vip') }
1627

1728
const plans = ref([
1829
{
@@ -72,9 +83,9 @@ const vipPosts = ref([
7283
const VIP_KEY = 'vp_member_unlock:*'
7384
const isVip = ref(localStorage.getItem(VIP_KEY) === '1')
7485
const clearUnlock = () => {
75-
try { localStorage.removeItem(VIP_KEY); isVip.value = false; alertify.success('已清除会员解锁') } catch (_) {}
86+
try { localStorage.removeItem(VIP_KEY); isVip.value = false; toast('success', '已清除会员解锁') } catch (_) {}
7687
}
77-
onMounted(() => { isVip.value = localStorage.getItem(VIP_KEY) === '1' })
88+
onMounted(async () => { isVip.value = localStorage.getItem(VIP_KEY) === '1'; await loadAlertify() })
7889

7990
const { theme } = useData()
8091
const inputCode = ref('')
@@ -86,7 +97,7 @@ const sha256 = async (s) => {
8697
}
8798
const activateVip = async () => {
8899
const v = inputCode.value.trim()
89-
if (!v) { alertify.warning('请输入激活码'); return }
100+
if (!v) { toast('warning', '请输入激活码'); return }
90101
const items = actList.map(s => String(s))
91102
const allHex = items.every(x => /^[0-9a-fA-F]{64}$/.test(x))
92103
let ok = false
@@ -99,9 +110,9 @@ const activateVip = async () => {
99110
if (ok) {
100111
localStorage.setItem(VIP_KEY, '1')
101112
isVip.value = true
102-
alertify.success('激活成功,VIP 已解锁')
113+
toast('success', '激活成功,VIP 已解锁')
103114
} else {
104-
alertify.error('激活码无效')
115+
toast('error', '激活码无效')
105116
}
106117
}
107118
</script>

0 commit comments

Comments
 (0)