@sugarat/theme 0.1.49 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/node.d.ts +41 -1
  2. package/node.js +24 -20
  3. package/package.json +24 -24
  4. package/src/components/BlogAlert.vue +17 -17
  5. package/src/components/BlogApp.vue +91 -50
  6. package/src/components/BlogArticleAnalyze.vue +56 -57
  7. package/src/components/BlogAuthor.vue +55 -0
  8. package/src/components/BlogComment.vue +53 -50
  9. package/src/components/BlogDocCover.vue +4 -4
  10. package/src/components/BlogFooter.vue +131 -0
  11. package/src/components/BlogFriendLink.vue +40 -31
  12. package/src/components/BlogHomeBanner.vue +22 -16
  13. package/src/components/BlogHomeInfo.vue +4 -0
  14. package/src/components/BlogHomeOverview.vue +20 -20
  15. package/src/components/BlogHomeTags.vue +49 -40
  16. package/src/components/BlogHotArticle.vue +43 -36
  17. package/src/components/BlogImagePreview.vue +7 -5
  18. package/src/components/BlogItem.vue +42 -43
  19. package/src/components/BlogList.vue +46 -42
  20. package/src/components/BlogPopover.vue +41 -39
  21. package/src/components/BlogRecommendArticle.vue +58 -48
  22. package/src/components/BlogSearch.vue +143 -145
  23. package/src/components/UserWorks.vue +214 -210
  24. package/src/composables/config/blog.ts +14 -5
  25. package/src/composables/config/index.ts +74 -31
  26. package/src/constants/svg.ts +11 -2
  27. package/src/index.ts +1 -2
  28. package/src/node.ts +2 -2
  29. package/src/styles/gongan.png +0 -0
  30. package/src/styles/scss/global.scss +0 -5
  31. package/src/utils/client/index.ts +9 -8
  32. package/src/utils/node/genFeed.ts +8 -7
  33. package/src/utils/node/index.ts +8 -6
  34. package/src/utils/node/mdPlugins.ts +29 -22
  35. package/src/utils/node/theme.ts +16 -13
  36. package/src/utils/node/vitePlugins.ts +7 -6
  37. package/types/vue-shim.d.ts +1 -1
@@ -1,47 +1,3 @@
1
- <template>
2
- <div class="doc-analyze" v-if="showAnalyze" data-pagefind-ignore="all">
3
- <span>
4
- <el-icon><EditPen /></el-icon>
5
- 字数:{{ wordCount }} 个字
6
- </span>
7
- <span>
8
- <el-icon><AlarmClock /></el-icon>
9
- 预计:{{ readTime }} 分钟
10
- </span>
11
- </div>
12
- <div class="meta-des" ref="$des" id="hack-article-des">
13
- <!-- TODO:是否需要原创?转载等标签,理论上可以添加标签解决,可以参考 charles7c -->
14
- <span v-if="author && !hiddenAuthor" class="author">
15
- <el-icon title="本文作者"><UserFilled /></el-icon>
16
- <a
17
- class="link"
18
- :href="currentAuthorInfo.url"
19
- :title="currentAuthorInfo.des"
20
- v-if="currentAuthorInfo"
21
- >
22
- {{ currentAuthorInfo.nickname }}
23
- </a>
24
- <template v-else>
25
- {{ author }}
26
- </template>
27
- </span>
28
- <span v-if="publishDate && !hiddenTime" class="publishDate">
29
- <el-icon :title="timeTitle"><Clock /></el-icon>
30
- {{ publishDate }}
31
- </span>
32
- <span v-if="tags.length" class="tags">
33
- <el-icon :title="timeTitle"><CollectionTag /></el-icon>
34
- <a class="link" :href="`/?tag=${tag}`" v-for="tag in tags" :key="tag"
35
- >{{ tag }}
36
- </a>
37
- </span>
38
- <!-- 封面展示 -->
39
- <ClientOnly>
40
- <BlogDocCover />
41
- </ClientOnly>
42
- </div>
43
- </template>
44
-
45
1
  <script lang="ts" setup>
46
2
  // 阅读时间计算方式参考
47
3
  // https://zhuanlan.zhihu.com/p/36375802
@@ -49,15 +5,15 @@ import { useData, useRoute } from 'vitepress'
49
5
  import { computed, onMounted, ref, watch } from 'vue'
50
6
  import { ElIcon } from 'element-plus'
51
7
  import {
52
- UserFilled,
8
+ AlarmClock,
53
9
  Clock,
10
+ CollectionTag,
54
11
  EditPen,
55
- AlarmClock,
56
- CollectionTag
12
+ UserFilled
57
13
  } from '@element-plus/icons-vue'
58
14
  import { useBlogConfig, useCurrentArticle } from '../composables/config/blog'
59
15
  import countWord, { formatShowDate } from '../utils/client'
60
- import { Theme } from '../composables/config'
16
+ import type { Theme } from '../composables/config'
61
17
  import BlogDocCover from './BlogDocCover.vue'
62
18
 
63
19
  const { article, authorList } = useBlogConfig()
@@ -69,7 +25,7 @@ const tags = computed(() => {
69
25
  []
70
26
  .concat(tag, tags, categories)
71
27
  .flat()
72
- .filter((v) => !!v)
28
+ .filter(v => !!v)
73
29
  )
74
30
  ]
75
31
  })
@@ -99,20 +55,20 @@ const readTime = computed(() => {
99
55
  const route = useRoute()
100
56
  const $des = ref<HTMLDivElement>()
101
57
 
102
- const analyze = () => {
58
+ function analyze() {
103
59
  if (!$des.value) {
104
60
  return
105
61
  }
106
- document.querySelectorAll('.meta-des').forEach((v) => v.remove())
62
+ document.querySelectorAll('.meta-des').forEach(v => v.remove())
107
63
  const docDomContainer = window.document.querySelector('#VPContent')
108
64
  const imgs = docDomContainer?.querySelectorAll<HTMLImageElement>(
109
65
  '.content-container .main img'
110
66
  )
111
67
  imageCount.value = imgs?.length || 0
112
68
 
113
- const words =
114
- docDomContainer?.querySelector('.content-container .main')?.textContent ||
115
- ''
69
+ const words
70
+ = docDomContainer?.querySelector('.content-container .main')?.textContent
71
+ || ''
116
72
 
117
73
  wordCount.value = countWord(words)
118
74
  docDomContainer?.querySelector('h1')?.after($des.value!)
@@ -151,11 +107,11 @@ const { theme } = useData<Theme.Config>()
151
107
  const globalAuthor = computed(() => theme.value.blog?.author || '')
152
108
  const author = computed(
153
109
  () =>
154
- (frontmatter.value.author || currentArticle.value?.meta.author) ??
155
- globalAuthor.value
110
+ (frontmatter.value.author || currentArticle.value?.meta.author)
111
+ ?? globalAuthor.value
156
112
  )
157
113
  const currentAuthorInfo = computed(() =>
158
- authorList?.find((v) => author.value === v.nickname)
114
+ authorList?.find(v => author.value === v.nickname)
159
115
  )
160
116
  const hiddenAuthor = computed(() => frontmatter.value.author === false)
161
117
 
@@ -171,6 +127,49 @@ watch(
171
127
  )
172
128
  </script>
173
129
 
130
+ <template>
131
+ <div v-if="showAnalyze" class="doc-analyze" data-pagefind-ignore="all">
132
+ <span>
133
+ <ElIcon><EditPen /></ElIcon>
134
+ 字数:{{ wordCount }} 个字
135
+ </span>
136
+ <span>
137
+ <ElIcon><AlarmClock /></ElIcon>
138
+ 预计:{{ readTime }} 分钟
139
+ </span>
140
+ </div>
141
+ <div id="hack-article-des" ref="$des" class="meta-des">
142
+ <!-- TODO:是否需要原创?转载等标签,理论上可以添加标签解决,可以参考 charles7c -->
143
+ <span v-if="author && !hiddenAuthor" class="author">
144
+ <ElIcon title="本文作者"><UserFilled /></ElIcon>
145
+ <a
146
+ v-if="currentAuthorInfo"
147
+ class="link"
148
+ :href="currentAuthorInfo.url"
149
+ :title="currentAuthorInfo.des"
150
+ >
151
+ {{ currentAuthorInfo.nickname }}
152
+ </a>
153
+ <template v-else>
154
+ {{ author }}
155
+ </template>
156
+ </span>
157
+ <span v-if="publishDate && !hiddenTime" class="publishDate">
158
+ <ElIcon :title="timeTitle"><Clock /></ElIcon>
159
+ {{ publishDate }}
160
+ </span>
161
+ <span v-if="tags.length" class="tags">
162
+ <ElIcon :title="timeTitle"><CollectionTag /></ElIcon>
163
+ <a v-for="tag in tags" :key="tag" class="link" :href="`/?tag=${tag}`">{{ tag }}
164
+ </a>
165
+ </span>
166
+ <!-- 封面展示 -->
167
+ <ClientOnly>
168
+ <BlogDocCover />
169
+ </ClientOnly>
170
+ </div>
171
+ </template>
172
+
174
173
  <style lang="scss" scoped>
175
174
  .doc-analyze {
176
175
  color: var(--vp-c-text-2);
@@ -0,0 +1,55 @@
1
+ <script setup>
2
+ import { useData } from 'vitepress'
3
+ import { computed } from 'vue'
4
+ import { useBlogConfig } from '../composables/config/blog'
5
+
6
+ const { home } = useBlogConfig()
7
+ const { frontmatter, site } = useData()
8
+ const author = computed(() =>
9
+ frontmatter.value.author
10
+ ?? frontmatter.value?.blog?.author
11
+ ?? home?.author
12
+ ?? site.value.themeConfig?.blog?.author
13
+ )
14
+ const logo = computed(() =>
15
+ frontmatter.value.logo
16
+ ?? frontmatter.value?.blog?.logo
17
+ ?? home?.logo
18
+ ?? site.value.themeConfig.logo
19
+ )
20
+ const show = computed(() => author.value || logo.value)
21
+ </script>
22
+
23
+ <template>
24
+ <div v-if="show" class="blog-author">
25
+ <img v-if="logo" :src="logo" alt="avatar">
26
+ <p v-if="author">
27
+ {{ author }}
28
+ </p>
29
+ </div>
30
+ </template>
31
+
32
+ <style scoped lang="scss">
33
+ .blog-author {
34
+ margin-bottom: 20px;
35
+
36
+ img {
37
+ display: block;
38
+ margin: 10px auto;
39
+ width: 100px;
40
+ height: 100px;
41
+ border-radius: 50%;
42
+ background-color: rgba(var(--bg-gradient-home));
43
+ }
44
+
45
+ img:hover {
46
+ transform: rotate(666turn);
47
+ transition-duration: 59s;
48
+ transition-timing-function: cubic-bezier(.34, 0, .84, 1)
49
+ }
50
+
51
+ p {
52
+ text-align: center;
53
+ }
54
+ }
55
+ </style>
@@ -1,47 +1,3 @@
1
- <template>
2
- <div
3
- class="comment"
4
- v-if="show"
5
- id="giscus-comment"
6
- data-pagefind-ignore="all"
7
- ref="commentEl"
8
- >
9
- <el-affix
10
- :class="{ hidden: commentIsVisible }"
11
- class="comment-btn"
12
- target="main"
13
- position="bottom"
14
- :offset="40"
15
- >
16
- <el-button
17
- @click="handleScrollToComment"
18
- plain
19
- :icon="Comment"
20
- type="primary"
21
- >评论</el-button
22
- >
23
- </el-affix>
24
- <component
25
- v-if="showComment"
26
- :is="'script'"
27
- src="https://giscus.app/client.js"
28
- :data-repo="commentConfig.repo"
29
- :data-repo-id="commentConfig.repoId"
30
- :data-category="commentConfig.category"
31
- :data-category-id="commentConfig.categoryId"
32
- :data-mapping="commentConfig.mapping || 'pathname'"
33
- data-reactions-enabled="1"
34
- data-emit-metadata="0"
35
- :data-input-position="commentConfig.inputPosition || 'top'"
36
- :data-theme="isDark ? 'dark' : 'light'"
37
- :data-lang="commentConfig.lang || 'zh-CN'"
38
- crossorigin="anonymous"
39
- :data-loading="commentConfig.loading || 'eager'"
40
- async
41
- >
42
- </component>
43
- </div>
44
- </template>
45
1
  <script setup lang="ts">
46
2
  import { useElementVisibility } from '@vueuse/core'
47
3
  import { useData, useRoute } from 'vitepress'
@@ -49,13 +5,13 @@ import { computed, nextTick, ref, watch } from 'vue'
49
5
  import { ElAffix, ElButton } from 'element-plus'
50
6
  import { Comment } from '@element-plus/icons-vue'
51
7
  import { useGiscusConfig } from '../composables/config/blog'
52
- import { Theme } from '../composables/config/index'
8
+ import type { Theme } from '../composables/config/index'
53
9
 
54
10
  const { frontmatter } = useData()
55
11
  const commentEl = ref(null)
56
12
  const commentIsVisible = useElementVisibility(commentEl)
57
13
 
58
- const handleScrollToComment = () => {
14
+ function handleScrollToComment() {
59
15
  document.querySelector('#giscus-comment')?.scrollIntoView({
60
16
  behavior: 'smooth',
61
17
  block: 'start'
@@ -78,10 +34,10 @@ const show = computed(() => {
78
34
  return giscusConfig
79
35
  }
80
36
  return (
81
- giscusConfig.repo &&
82
- giscusConfig.repoId &&
83
- giscusConfig.category &&
84
- giscusConfig.categoryId
37
+ giscusConfig.repo
38
+ && giscusConfig.repoId
39
+ && giscusConfig.category
40
+ && giscusConfig.categoryId
85
41
  )
86
42
  })
87
43
 
@@ -110,6 +66,53 @@ watch(isDark, () => {
110
66
  })
111
67
  })
112
68
  </script>
69
+
70
+ <template>
71
+ <div
72
+ v-if="show"
73
+ id="giscus-comment"
74
+ ref="commentEl"
75
+ class="comment"
76
+ data-pagefind-ignore="all"
77
+ >
78
+ <ElAffix
79
+ :class="{ hidden: commentIsVisible }"
80
+ class="comment-btn"
81
+ target="main"
82
+ position="bottom"
83
+ :offset="40"
84
+ >
85
+ <ElButton
86
+ plain
87
+ :icon="Comment"
88
+ type="primary"
89
+ @click="handleScrollToComment"
90
+ >
91
+ 评论
92
+ </ElButton>
93
+ </ElAffix>
94
+ <!-- eslint-disable-next-line vue/require-component-is -->
95
+ <component
96
+ is="script"
97
+ v-if="showComment"
98
+ src="https://giscus.app/client.js"
99
+ :data-repo="commentConfig.repo"
100
+ :data-repo-id="commentConfig.repoId"
101
+ :data-category="commentConfig.category"
102
+ :data-category-id="commentConfig.categoryId"
103
+ :data-mapping="commentConfig.mapping || 'pathname'"
104
+ data-reactions-enabled="1"
105
+ data-emit-metadata="0"
106
+ :data-input-position="commentConfig.inputPosition || 'top'"
107
+ :data-theme="isDark ? 'dark' : 'light'"
108
+ :data-lang="commentConfig.lang || 'zh-CN'"
109
+ crossorigin="anonymous"
110
+ :data-loading="commentConfig.loading || 'eager'"
111
+ async
112
+ />
113
+ </div>
114
+ </template>
115
+
113
116
  <style scoped lang="scss">
114
117
  .comment {
115
118
  width: 100%;
@@ -1,7 +1,3 @@
1
- <template>
2
- <img class="blog-doc-cover" v-if="cover && !hiddenCover" :src="cover" />
3
- </template>
4
-
5
1
  <script lang="ts" setup>
6
2
  import { useData } from 'vitepress'
7
3
  import { computed } from 'vue'
@@ -15,6 +11,10 @@ const hiddenCover = computed(
15
11
  )
16
12
  </script>
17
13
 
14
+ <template>
15
+ <img v-if="cover && !hiddenCover" class="blog-doc-cover" :src="cover">
16
+ </template>
17
+
18
18
  <style lang="scss" scoped>
19
19
  img.blog-doc-cover.blog-doc-cover.blog-doc-cover {
20
20
  width: 100%;
@@ -0,0 +1,131 @@
1
+ <script setup lang="ts">
2
+ import { onMounted, ref } from 'vue'
3
+ import { useHomeFooterConfig } from '../composables/config/blog'
4
+ import packageJSON from '../../package.json'
5
+ import { copyrightSVG, icpSVG, themeSVG } from '../constants/svg'
6
+
7
+ const footer = useHomeFooterConfig()
8
+ const data = ref<{
9
+ name: string
10
+ link?: string
11
+ icon?: string | boolean
12
+ }[]>([])
13
+ onMounted(() => {
14
+ if (!footer) {
15
+ return
16
+ }
17
+ const { icpRecord, securityRecord, copyright, version } = footer
18
+ // version
19
+ if (version !== false) {
20
+ data.value.push({
21
+ name: `@sugarat/theme@${packageJSON.version}`,
22
+ link: 'https://www.npmjs.com/package/@sugarat/create-theme',
23
+ icon: themeSVG
24
+ })
25
+ }
26
+ // copyright
27
+ if (typeof copyright === 'string') {
28
+ data.value.push({
29
+ name: copyright,
30
+ icon: copyrightSVG
31
+ })
32
+ }
33
+ if (copyright instanceof Object) {
34
+ data.value.push({
35
+ icon: copyrightSVG,
36
+ name: copyright.message,
37
+ ...copyright
38
+ })
39
+ }
40
+ // 备案信息
41
+ if (icpRecord) {
42
+ data.value.push({
43
+ icon: icpSVG,
44
+ ...icpRecord
45
+ })
46
+ }
47
+ // 网备信息
48
+ if (securityRecord) {
49
+ data.value.push({
50
+ icon: 'security',
51
+ ...securityRecord
52
+ })
53
+ }
54
+ })
55
+ </script>
56
+
57
+ <template>
58
+ <footer v-if="footer" class="blog-footer">
59
+ <p v-if="footer?.message" v-html="footer?.message" />
60
+ <p class="footer-item-list">
61
+ <span v-for="(item, index) in data" :key="index" class="footer-item">
62
+ <i v-if="item.icon === 'security'">
63
+ <img src="./../styles/gongan.png" alt="公网安备">
64
+ </i>
65
+ <i v-else-if="item.icon" v-html="item.icon" />
66
+ <a v-if="item.link" :href="item.link" target="_blank" rel="noopener noreferrer">
67
+ {{ item.name }}
68
+ </a>
69
+ <span v-else>{{ item.name }}</span>
70
+ </span>
71
+ </p>
72
+ </footer>
73
+ </template>
74
+
75
+ <style lang="scss" scoped>
76
+ footer.blog-footer {
77
+ text-align: center;
78
+ position: relative;
79
+ border-top: 1px solid var(--vp-c-gutter);
80
+ padding: 20px 12px;
81
+ background-color: var(--vp-c-bg);
82
+
83
+ p {
84
+ line-height: 24px;
85
+ font-size: 14px;
86
+ font-weight: 500;
87
+ color: var(--vp-c-text-2);
88
+ }
89
+ }
90
+
91
+ .footer-item-list {
92
+ display: flex;
93
+ justify-content: center;
94
+ flex-wrap: wrap;
95
+ }
96
+
97
+ @media (max-width: 719px) {
98
+ .footer-item-list {
99
+ flex-direction: column;
100
+ align-items: center;
101
+ }
102
+ }
103
+
104
+ .footer-item {
105
+ display: flex;
106
+ align-items: center;
107
+ margin: 0 8px;
108
+
109
+ i {
110
+ margin-right: 4px;
111
+ }
112
+
113
+ i :deep(svg) {
114
+ fill: var(--vp-c-text-2);
115
+ width: 16px;
116
+ height: 16px;
117
+ }
118
+
119
+ i :deep(img) {
120
+ width: 16px;
121
+ height: 16px;
122
+ }
123
+
124
+ a:hover {
125
+ color: var(--vp-c-brand-1);
126
+ text-decoration: underline;
127
+ text-decoration-color: var(--vp-c-brand-1);
128
+ text-decoration-style: dashed;
129
+ }
130
+ }
131
+ </style>
@@ -1,33 +1,3 @@
1
- <template>
2
- <div class="card friend-wrapper" v-if="friendList?.length">
3
- <!-- 头部 -->
4
- <div class="card-header">
5
- <span class="title svg-icon"><svg width="512" height="512" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
6
- <path fill="#EF9645"
7
- d="M16.428 30.331a2.31 2.31 0 0 0 3.217-.568a.798.798 0 0 0-.197-1.114l-1.85-1.949l4.222 2.955a1.497 1.497 0 0 0 2.089-.369a1.5 1.5 0 0 0-.369-2.089l-3.596-3.305l5.375 3.763a1.497 1.497 0 0 0 2.089-.369a1.5 1.5 0 0 0-.369-2.089l-4.766-4.073l5.864 4.105a1.497 1.497 0 0 0 2.089-.369a1.5 1.5 0 0 0-.369-2.089L4.733 11.194l-3.467 5.521c-.389.6-.283 1.413.276 1.891l7.786 6.671c.355.304.724.591 1.107.859l5.993 4.195z" />
8
- <path fill="#FFDC5D"
9
- d="M29.802 21.752L18.5 13.601l-.059-.08l.053-.08l.053-.053l.854.469c.958.62 3.147 1.536 4.806 1.536c1.135 0 1.815-.425 2.018-1.257a1.409 1.409 0 0 0-1.152-1.622a6.788 6.788 0 0 1-2.801-1.091l-.555-.373c-.624-.421-1.331-.898-1.853-1.206c-.65-.394-1.357-.585-2.163-.585c-1.196 0-2.411.422-3.585.83l-1.266.436a5.18 5.18 0 0 1-1.696.271c-1.544 0-3.055-.586-4.516-1.152l-.147-.058a1.389 1.389 0 0 0-1.674.56L1.35 15.669a1.357 1.357 0 0 0 .257 1.761l7.785 6.672c.352.301.722.588 1.1.852l6.165 4.316a2 2 0 0 0 2.786-.491a.803.803 0 0 0-.196-1.115l-1.833-1.283a.424.424 0 0 1-.082-.618a.422.422 0 0 1 .567-.075l3.979 2.785a1.4 1.4 0 0 0 1.606-2.294l-3.724-2.606a.424.424 0 0 1-.082-.618a.423.423 0 0 1 .567-.075l5.132 3.593a1.4 1.4 0 0 0 1.606-2.294l-4.868-3.407a.42.42 0 0 1-.081-.618a.377.377 0 0 1 .506-.066l5.656 3.959a1.4 1.4 0 0 0 1.606-2.295z" />
10
- <path fill="#EF9645"
11
- d="M16.536 27.929c-.07.267-.207.498-.389.681l-1.004.996a1.494 1.494 0 0 1-1.437.396a1.5 1.5 0 0 1-.683-2.512l1.004-.996a1.494 1.494 0 0 1 1.437-.396a1.502 1.502 0 0 1 1.072 1.831zM5.992 23.008l1.503-1.497a1.5 1.5 0 0 0-.444-2.429a1.495 1.495 0 0 0-1.674.31l-1.503 1.497a1.5 1.5 0 0 0 .445 2.429a1.496 1.496 0 0 0 1.673-.31zm5.204.052a1.5 1.5 0 1 0-2.122-2.118L6.072 23.94a1.5 1.5 0 1 0 2.122 2.118l3.002-2.998zm2.25 3a1.5 1.5 0 0 0-.945-2.555a1.489 1.489 0 0 0-1.173.44L9.323 25.94a1.5 1.5 0 0 0 .945 2.556c.455.036.874-.141 1.173-.44l2.005-1.996zm16.555-4.137l.627-.542l-6.913-10.85l-12.27 1.985a1.507 1.507 0 0 0-1.235 1.737c.658 2.695 6.003.693 8.355-.601l11.436 8.271z" />
12
- <path fill="#FFCC4D"
13
- d="M16.536 26.929c-.07.267-.207.498-.389.681l-1.004.996a1.494 1.494 0 0 1-1.437.396a1.5 1.5 0 0 1-.683-2.512l1.004-.996a1.494 1.494 0 0 1 1.437-.396a1.502 1.502 0 0 1 1.072 1.831zM5.992 22.008l1.503-1.497a1.5 1.5 0 0 0-.444-2.429a1.497 1.497 0 0 0-1.674.31l-1.503 1.497a1.5 1.5 0 0 0 .445 2.429a1.496 1.496 0 0 0 1.673-.31zm5.204.052a1.5 1.5 0 1 0-2.122-2.118L6.072 22.94a1.5 1.5 0 1 0 2.122 2.118l3.002-2.998zm2.25 3a1.5 1.5 0 0 0-.945-2.555a1.489 1.489 0 0 0-1.173.44L9.323 24.94a1.5 1.5 0 0 0 .945 2.556c.455.036.874-.141 1.173-.44l2.005-1.996zm21.557-7.456a1.45 1.45 0 0 0 .269-1.885l-.003-.005l-3.467-6.521a1.488 1.488 0 0 0-1.794-.6c-1.992.771-4.174 1.657-6.292.937l-1.098-.377c-1.948-.675-4.066-1.466-6-.294c-.695.409-1.738 1.133-2.411 1.58a6.873 6.873 0 0 1-2.762 1.076a1.502 1.502 0 0 0-1.235 1.737c.613 2.512 5.3.908 7.838-.369a.968.968 0 0 1 1.002.081l11.584 8.416l4.369-3.776z" />
14
- </svg> 友情链接</span>
15
- </div>
16
- <!-- 文章列表 -->
17
- <ol class="friend-list">
18
- <li v-for="v in friendList" :key="v.nickname">
19
- <a :href="v.url" target="_blank">
20
- <el-avatar :size="50" :src="v.avatar" :alt="v.alt" />
21
- <div>
22
- <span class="nickname">{{ v.nickname }}</span>
23
- <p class="des">{{ v.des }}</p>
24
- </div>
25
- </a>
26
- </li>
27
- </ol>
28
- </div>
29
- </template>
30
-
31
1
  <script lang="ts" setup>
32
2
  import { ElAvatar } from 'element-plus'
33
3
  import { useDark } from '@vueuse/core'
@@ -58,6 +28,44 @@ const friendList = computed(() => {
58
28
  })
59
29
  </script>
60
30
 
31
+ <template>
32
+ <div v-if="friendList?.length" class="card friend-wrapper">
33
+ <!-- 头部 -->
34
+ <div class="card-header">
35
+ <span class="title svg-icon"><svg width="512" height="512" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
36
+ <path
37
+ fill="#EF9645"
38
+ d="M16.428 30.331a2.31 2.31 0 0 0 3.217-.568a.798.798 0 0 0-.197-1.114l-1.85-1.949l4.222 2.955a1.497 1.497 0 0 0 2.089-.369a1.5 1.5 0 0 0-.369-2.089l-3.596-3.305l5.375 3.763a1.497 1.497 0 0 0 2.089-.369a1.5 1.5 0 0 0-.369-2.089l-4.766-4.073l5.864 4.105a1.497 1.497 0 0 0 2.089-.369a1.5 1.5 0 0 0-.369-2.089L4.733 11.194l-3.467 5.521c-.389.6-.283 1.413.276 1.891l7.786 6.671c.355.304.724.591 1.107.859l5.993 4.195z"
39
+ />
40
+ <path
41
+ fill="#FFDC5D"
42
+ d="M29.802 21.752L18.5 13.601l-.059-.08l.053-.08l.053-.053l.854.469c.958.62 3.147 1.536 4.806 1.536c1.135 0 1.815-.425 2.018-1.257a1.409 1.409 0 0 0-1.152-1.622a6.788 6.788 0 0 1-2.801-1.091l-.555-.373c-.624-.421-1.331-.898-1.853-1.206c-.65-.394-1.357-.585-2.163-.585c-1.196 0-2.411.422-3.585.83l-1.266.436a5.18 5.18 0 0 1-1.696.271c-1.544 0-3.055-.586-4.516-1.152l-.147-.058a1.389 1.389 0 0 0-1.674.56L1.35 15.669a1.357 1.357 0 0 0 .257 1.761l7.785 6.672c.352.301.722.588 1.1.852l6.165 4.316a2 2 0 0 0 2.786-.491a.803.803 0 0 0-.196-1.115l-1.833-1.283a.424.424 0 0 1-.082-.618a.422.422 0 0 1 .567-.075l3.979 2.785a1.4 1.4 0 0 0 1.606-2.294l-3.724-2.606a.424.424 0 0 1-.082-.618a.423.423 0 0 1 .567-.075l5.132 3.593a1.4 1.4 0 0 0 1.606-2.294l-4.868-3.407a.42.42 0 0 1-.081-.618a.377.377 0 0 1 .506-.066l5.656 3.959a1.4 1.4 0 0 0 1.606-2.295z"
43
+ />
44
+ <path
45
+ fill="#EF9645"
46
+ d="M16.536 27.929c-.07.267-.207.498-.389.681l-1.004.996a1.494 1.494 0 0 1-1.437.396a1.5 1.5 0 0 1-.683-2.512l1.004-.996a1.494 1.494 0 0 1 1.437-.396a1.502 1.502 0 0 1 1.072 1.831zM5.992 23.008l1.503-1.497a1.5 1.5 0 0 0-.444-2.429a1.495 1.495 0 0 0-1.674.31l-1.503 1.497a1.5 1.5 0 0 0 .445 2.429a1.496 1.496 0 0 0 1.673-.31zm5.204.052a1.5 1.5 0 1 0-2.122-2.118L6.072 23.94a1.5 1.5 0 1 0 2.122 2.118l3.002-2.998zm2.25 3a1.5 1.5 0 0 0-.945-2.555a1.489 1.489 0 0 0-1.173.44L9.323 25.94a1.5 1.5 0 0 0 .945 2.556c.455.036.874-.141 1.173-.44l2.005-1.996zm16.555-4.137l.627-.542l-6.913-10.85l-12.27 1.985a1.507 1.507 0 0 0-1.235 1.737c.658 2.695 6.003.693 8.355-.601l11.436 8.271z"
47
+ />
48
+ <path
49
+ fill="#FFCC4D"
50
+ d="M16.536 26.929c-.07.267-.207.498-.389.681l-1.004.996a1.494 1.494 0 0 1-1.437.396a1.5 1.5 0 0 1-.683-2.512l1.004-.996a1.494 1.494 0 0 1 1.437-.396a1.502 1.502 0 0 1 1.072 1.831zM5.992 22.008l1.503-1.497a1.5 1.5 0 0 0-.444-2.429a1.497 1.497 0 0 0-1.674.31l-1.503 1.497a1.5 1.5 0 0 0 .445 2.429a1.496 1.496 0 0 0 1.673-.31zm5.204.052a1.5 1.5 0 1 0-2.122-2.118L6.072 22.94a1.5 1.5 0 1 0 2.122 2.118l3.002-2.998zm2.25 3a1.5 1.5 0 0 0-.945-2.555a1.489 1.489 0 0 0-1.173.44L9.323 24.94a1.5 1.5 0 0 0 .945 2.556c.455.036.874-.141 1.173-.44l2.005-1.996zm21.557-7.456a1.45 1.45 0 0 0 .269-1.885l-.003-.005l-3.467-6.521a1.488 1.488 0 0 0-1.794-.6c-1.992.771-4.174 1.657-6.292.937l-1.098-.377c-1.948-.675-4.066-1.466-6-.294c-.695.409-1.738 1.133-2.411 1.58a6.873 6.873 0 0 1-2.762 1.076a1.502 1.502 0 0 0-1.235 1.737c.613 2.512 5.3.908 7.838-.369a.968.968 0 0 1 1.002.081l11.584 8.416l4.369-3.776z"
51
+ />
52
+ </svg> 友情链接</span>
53
+ </div>
54
+ <!-- 文章列表 -->
55
+ <ol class="friend-list">
56
+ <li v-for="v in friendList" :key="v.nickname">
57
+ <a :href="v.url" target="_blank">
58
+ <ElAvatar :size="50" :src="v.avatar" :alt="v.alt" />
59
+ <div>
60
+ <span class="nickname">{{ v.nickname }}</span>
61
+ <p class="des">{{ v.des }}</p>
62
+ </div>
63
+ </a>
64
+ </li>
65
+ </ol>
66
+ </div>
67
+ </template>
68
+
61
69
  <style lang="scss" scoped>
62
70
  .card {
63
71
  position: relative;
@@ -128,4 +136,5 @@ const friendList = computed(() => {
128
136
  font-size: 14px;
129
137
  }
130
138
  }
131
- }</style>
139
+ }
140
+ </style>
@@ -1,14 +1,3 @@
1
- <template>
2
- <div>
3
- <h1>
4
- <span class="name">{{ name }}</span>
5
- <span class="motto" v-show="motto">{{ motto }}</span>
6
- </h1>
7
- <div class="inspiring-wrapper">
8
- <h2 @click="changeSlogan" v-show="!!inspiring">{{ inspiring }}</h2>
9
- </div>
10
- </div>
11
- </template>
12
1
  <script setup lang="ts">
13
2
  import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
14
3
  import { useData } from 'vitepress'
@@ -28,7 +17,7 @@ const inspiringList = computed<string[]>(() => {
28
17
  ...new Set(
29
18
  [frontmatter.value.blog?.inspiring, home?.inspiring]
30
19
  .flat()
31
- .filter((v) => !!v)
20
+ .filter(v => !!v)
32
21
  )
33
22
  ]
34
23
  })
@@ -41,7 +30,7 @@ watch(inspiringTimeout, () => {
41
30
  startTimer()
42
31
  })
43
32
  const timer = ref<any>(0)
44
- const startTimer = () => {
33
+ function startTimer() {
45
34
  if (timer.value) {
46
35
  clearTimeout(timer.value)
47
36
  }
@@ -61,15 +50,17 @@ onUnmounted(() => {
61
50
  }
62
51
  })
63
52
 
64
- const changeSlogan = async () => {
53
+ async function changeSlogan() {
65
54
  // 顺手启动定时器
66
55
  startTimer()
67
56
 
68
- if (inspiringList.value.length < 1) return
57
+ if (inspiringList.value.length < 1)
58
+ return
69
59
 
70
60
  inspiringIndex.value = (inspiringIndex.value + 1) % inspiringList.value.length
71
61
  const newValue = inspiringList.value[inspiringIndex.value]
72
- if (newValue === inspiring.value) return
62
+ if (newValue === inspiring.value)
63
+ return
73
64
 
74
65
  // 重新渲染数据,同时触发动画
75
66
  inspiring.value = ''
@@ -78,6 +69,21 @@ const changeSlogan = async () => {
78
69
  }, 100)
79
70
  }
80
71
  </script>
72
+
73
+ <template>
74
+ <div>
75
+ <h1>
76
+ <span class="name">{{ name }}</span>
77
+ <span v-show="motto" class="motto">{{ motto }}</span>
78
+ </h1>
79
+ <div class="inspiring-wrapper">
80
+ <h2 v-show="!!inspiring" @click="changeSlogan">
81
+ {{ inspiring }}
82
+ </h2>
83
+ </div>
84
+ </div>
85
+ </template>
86
+
81
87
  <style lang="scss" scoped>
82
88
  h1 {
83
89
  text-align: center;
@@ -3,10 +3,14 @@ import BlogHomeOverview from './BlogHomeOverview.vue'
3
3
  import BlogHotArticle from './BlogHotArticle.vue'
4
4
  import BlogHomeTags from './BlogHomeTags.vue'
5
5
  import BlogFriendLink from './BlogFriendLink.vue'
6
+ import BlogAuthor from './BlogAuthor.vue'
6
7
  </script>
7
8
 
8
9
  <template>
9
10
  <div class="blog-info" data-pagefind-ignore="all">
11
+ <!-- 头像信息 -->
12
+ <BlogAuthor />
13
+
10
14
  <!-- 统计数据,日后支持,点击筛选出左侧的数据 -->
11
15
  <BlogHomeOverview />
12
16