3636 </template >
3737 </div >
3838
39- <!-- 外挂式文章列表:左侧抽屉 -->
40- <button
41- class =" articles-fab"
42- type =" button"
43- :aria-expanded =" listOpen ? 'true' : 'false'"
44- aria-controls =" articles-drawer"
45- @click =" openList"
46- >
47- 博客
48- </button >
49-
50- <div v-if =" listOpen" class =" articles-overlay" @click =" closeList" />
51- <aside
52- id =" articles-drawer"
53- class =" articles-drawer"
54- :class =" { open: listOpen }"
55- role =" dialog"
56- aria-label =" 博客文章列表"
57- >
58- <div class =" articles-drawer-head" >
59- <div class =" articles-drawer-title" >博客</div >
60- <button class =" articles-drawer-close" type =" button" aria-label =" 关闭文章列表" @click =" closeList" >
61- ×
62- </button >
63- </div >
64-
65- <div class =" articles-drawer-search" >
66- <input v-model =" keyword" type =" text" class =" search-input" placeholder =" find something..." />
67- </div >
68-
69- <div class =" articles-drawer-body" >
70- <div v-if =" loadingList" class =" sidebar-hint" style =" color : var (--muted )" >加载中...</div >
71- <div v-else-if =" listItems.length === 0" class =" sidebar-hint" style =" color : var (--muted )" >暂无文章</div >
72-
73- <button
74- v-for =" a in listItems"
75- :key =" a.id"
76- class =" sidebar-item articles-drawer-item"
77- :class =" { active: String(a.id) === String(route.params.id) }"
78- type =" button"
79- @click =" goArticle(a.id)"
80- >
81- <div class =" sidebar-item-title" >{{ a.title }}</div >
82- <div class =" sidebar-item-date" >{{ formatDate(a.publishedAt) }}</div >
83- </button >
84- </div >
85- </aside >
86-
8739 <!-- 外挂式目录:左侧抽屉 -->
8840 <button
8941 v-if =" tocItems.length"
12779</template >
12880
12981<script setup>
130- import { computed , onBeforeUnmount , onMounted , ref , watchEffect } from ' vue'
131- import { useRoute , useRouter } from ' vue-router'
82+ import { onBeforeUnmount , onMounted , ref , watchEffect } from ' vue'
83+ import { useRoute } from ' vue-router'
13284import DOMPurify from ' dompurify'
13385import { marked } from ' marked'
13486
135- import { fetchArticleDetail , fetchArticles , coverUrl } from ' ../api/article'
87+ import { fetchArticleDetail , coverUrl } from ' ../api/article'
13688import CommentSection from ' ../components/CommentSection.vue'
13789
13890const route = useRoute ()
139- const router = useRouter ()
14091const loading = ref (true )
14192const article = ref ({})
14293const html = ref (' ' )
@@ -153,43 +104,6 @@ onMounted(async () => {
153104 }
154105})
155106
156- const listOpen = ref (false )
157- const loadingList = ref (true )
158- const keyword = ref (' ' )
159- const allArticles = ref ([])
160-
161- onMounted (async () => {
162- try {
163- loadingList .value = true
164- const resp = await fetchArticles ({ page: 0 , size: 200 })
165- allArticles .value = resp? .items || []
166- } finally {
167- loadingList .value = false
168- }
169- })
170-
171- const listItems = computed (() => {
172- const k = (keyword .value || ' ' ).trim ().toLowerCase ()
173- if (! k) return allArticles .value
174- return allArticles .value .filter (a => (a .title || ' ' ).toLowerCase ().includes (k))
175- })
176-
177- function openList () {
178- listOpen .value = true
179- }
180-
181- function closeList () {
182- listOpen .value = false
183- }
184-
185- function goArticle (id ) {
186- closeList ()
187- tocOpen .value = false
188- router .push (` /article/${ id} ` ).then (() => {
189- window .scrollTo ({ top: 0 , behavior: ' auto' })
190- })
191- }
192-
193107watchEffect (() => {
194108 const md = article .value ? .contentMarkdown || ' '
195109 const rawHtml = marked .parse (md)
@@ -236,7 +150,6 @@ function jumpToHeading(id) {
236150
237151function onKeyDown (e ) {
238152 if (e .key === ' Escape' ) {
239- if (listOpen .value ) listOpen .value = false
240153 if (tocOpen .value ) tocOpen .value = false
241154 }
242155}
0 commit comments