@sugarat/theme 0.1.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.
- package/LICENSE +21 -0
- package/README.md +35 -0
- package/node.d.ts +174 -0
- package/node.js +185 -0
- package/package.json +57 -0
- package/src/components/BlogAlert.vue +74 -0
- package/src/components/BlogApp.vue +103 -0
- package/src/components/BlogArticleAnalyze.vue +154 -0
- package/src/components/BlogComment.vue +131 -0
- package/src/components/BlogFriendLink.vue +94 -0
- package/src/components/BlogHomeBanner.vue +105 -0
- package/src/components/BlogHomeInfo.vue +38 -0
- package/src/components/BlogHomeOverview.vue +97 -0
- package/src/components/BlogHomeTags.vue +123 -0
- package/src/components/BlogHotArticle.vue +177 -0
- package/src/components/BlogImagePreview.vue +52 -0
- package/src/components/BlogItem.vue +112 -0
- package/src/components/BlogList.vue +75 -0
- package/src/components/BlogPopover.vue +218 -0
- package/src/components/BlogRecommendArticle.vue +173 -0
- package/src/components/BlogSearch.vue +198 -0
- package/src/components/BlogSidebar.vue +19 -0
- package/src/composables/config/blog.ts +96 -0
- package/src/composables/config/index.ts +159 -0
- package/src/index.ts +18 -0
- package/src/node.ts +175 -0
- package/src/styles/bg.png +0 -0
- package/src/styles/index.scss +95 -0
- package/src/utils/index.ts +84 -0
- package/types/vue-shim.d.ts +6 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="doc-analyze" v-if="showAnalyze">
|
|
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
|
+
<span v-if="author">
|
|
14
|
+
<el-icon><UserFilled /></el-icon>
|
|
15
|
+
{{ author }}
|
|
16
|
+
</span>
|
|
17
|
+
<span>
|
|
18
|
+
<el-icon><Clock /></el-icon>
|
|
19
|
+
{{ publishDate }}
|
|
20
|
+
</span>
|
|
21
|
+
</div>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<script lang="ts" setup>
|
|
25
|
+
// 阅读时间计算方式参考
|
|
26
|
+
// https://zhuanlan.zhihu.com/p/36375802
|
|
27
|
+
import { useData, useRoute } from 'vitepress'
|
|
28
|
+
import { computed, onMounted, ref, watch } from 'vue'
|
|
29
|
+
import { ElIcon } from 'element-plus'
|
|
30
|
+
import { UserFilled, Clock, EditPen, AlarmClock } from '@element-plus/icons-vue'
|
|
31
|
+
import { useBlogConfig, useCurrentArticle } from '../composables/config/blog'
|
|
32
|
+
import countWord, { formatShowDate } from '../utils/index'
|
|
33
|
+
import { Theme } from '../composables/config'
|
|
34
|
+
|
|
35
|
+
const { article } = useBlogConfig()
|
|
36
|
+
const { frontmatter } = useData()
|
|
37
|
+
const showAnalyze = computed(
|
|
38
|
+
() => frontmatter.value?.readingTime ?? article?.readingTime ?? true
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
const wordCount = ref(0)
|
|
42
|
+
const imageCount = ref(0)
|
|
43
|
+
const wordTime = computed(() => {
|
|
44
|
+
return ~~((wordCount.value / 275) * 60)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const imageTime = computed(() => {
|
|
48
|
+
const n = imageCount.value
|
|
49
|
+
if (imageCount.value <= 10) {
|
|
50
|
+
// 等差数列求和
|
|
51
|
+
return n * 13 + (n * (n - 1)) / 2
|
|
52
|
+
}
|
|
53
|
+
return 175 + (n - 10) * 3
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const readTime = computed(() => {
|
|
57
|
+
return Math.ceil((wordTime.value + imageTime.value) / 60)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const route = useRoute()
|
|
61
|
+
const $des = ref<HTMLDivElement>()
|
|
62
|
+
|
|
63
|
+
const analyze = () => {
|
|
64
|
+
if (!$des.value) {
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
const docDomContainer = window.document.querySelector('#VPContent')
|
|
68
|
+
const imgs = docDomContainer?.querySelectorAll<HTMLImageElement>(
|
|
69
|
+
'.content-container .main img'
|
|
70
|
+
)
|
|
71
|
+
imageCount.value = imgs?.length || 0
|
|
72
|
+
|
|
73
|
+
const words =
|
|
74
|
+
docDomContainer?.querySelector('.content-container .main')?.textContent ||
|
|
75
|
+
''
|
|
76
|
+
|
|
77
|
+
wordCount.value = countWord(words)
|
|
78
|
+
docDomContainer?.querySelector('h1')?.after($des.value!)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
onMounted(() => {
|
|
82
|
+
const observer = new MutationObserver(() => {
|
|
83
|
+
const targetInstance = document.querySelector('#hack-article-des')
|
|
84
|
+
if (!targetInstance) {
|
|
85
|
+
analyze()
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
observer.observe(document.body, {
|
|
89
|
+
childList: true, // 观察目标子节点的变化,是否有添加或者删除
|
|
90
|
+
subtree: true // 观察后代节点,默认为 false
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// 初始化时执行一次
|
|
94
|
+
analyze()
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
// 阅读量
|
|
98
|
+
const pv = ref(6666)
|
|
99
|
+
|
|
100
|
+
const currentArticle = useCurrentArticle()
|
|
101
|
+
const publishDate = computed(() => {
|
|
102
|
+
return formatShowDate(currentArticle.value?.meta?.date || '')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
const { theme } = useData<Theme.Config>()
|
|
106
|
+
const globalAuthor = computed(() => theme.value.blog.author || '')
|
|
107
|
+
const author = computed(
|
|
108
|
+
() => currentArticle.value?.meta.author || globalAuthor.value
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
watch(
|
|
112
|
+
() => route.path,
|
|
113
|
+
() => {
|
|
114
|
+
// TODO: 调用接口取数据
|
|
115
|
+
pv.value = 123
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
immediate: true
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
</script>
|
|
122
|
+
|
|
123
|
+
<style lang="scss" scoped>
|
|
124
|
+
.doc-analyze {
|
|
125
|
+
color: var(--vp-c-text-2);
|
|
126
|
+
font-size: 14px;
|
|
127
|
+
margin-bottom: 20px;
|
|
128
|
+
display: flex;
|
|
129
|
+
justify-content: center;
|
|
130
|
+
span {
|
|
131
|
+
margin-right: 16px;
|
|
132
|
+
display: flex;
|
|
133
|
+
align-items: center;
|
|
134
|
+
.el-icon {
|
|
135
|
+
margin-right: 4px;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
.meta-des {
|
|
140
|
+
text-align: left;
|
|
141
|
+
color: var(--vp-c-text-2);
|
|
142
|
+
font-size: 14px;
|
|
143
|
+
margin-top: 6px;
|
|
144
|
+
display: flex;
|
|
145
|
+
span {
|
|
146
|
+
margin-right: 16px;
|
|
147
|
+
display: flex;
|
|
148
|
+
align-items: center;
|
|
149
|
+
.el-icon {
|
|
150
|
+
margin-right: 4px;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
</style>
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="comment" v-if="show" id="giscus-comment">
|
|
3
|
+
<el-affix
|
|
4
|
+
:class="{ hidden: !showCommnetAffix }"
|
|
5
|
+
class="comment-btn"
|
|
6
|
+
target="main"
|
|
7
|
+
position="bottom"
|
|
8
|
+
@change="handleVisibleChange"
|
|
9
|
+
:offset="40"
|
|
10
|
+
>
|
|
11
|
+
<el-button
|
|
12
|
+
@click="handleScrollToComment"
|
|
13
|
+
plain
|
|
14
|
+
:icon="Comment"
|
|
15
|
+
type="primary"
|
|
16
|
+
>评论</el-button
|
|
17
|
+
>
|
|
18
|
+
</el-affix>
|
|
19
|
+
<component
|
|
20
|
+
v-if="showComment"
|
|
21
|
+
:is="'script'"
|
|
22
|
+
src="https://giscus.app/client.js"
|
|
23
|
+
:data-repo="commentConfig.repo"
|
|
24
|
+
:data-repo-id="commentConfig.repoId"
|
|
25
|
+
:data-category="commentConfig.category"
|
|
26
|
+
:data-category-id="commentConfig.categoryId"
|
|
27
|
+
:data-mapping="commentConfig.mapping || 'pathname'"
|
|
28
|
+
data-reactions-enabled="1"
|
|
29
|
+
data-emit-metadata="0"
|
|
30
|
+
:data-input-position="commentConfig.inputPosition || 'top'"
|
|
31
|
+
:data-theme="isDark ? 'dark' : 'light'"
|
|
32
|
+
:data-lang="commentConfig.lang || 'zh-CN'"
|
|
33
|
+
crossorigin="anonymous"
|
|
34
|
+
:data-loading="commentConfig.loading || ''"
|
|
35
|
+
async
|
|
36
|
+
>
|
|
37
|
+
</component>
|
|
38
|
+
</div>
|
|
39
|
+
</template>
|
|
40
|
+
<script setup lang="ts">
|
|
41
|
+
import { useDark } from '@vueuse/core'
|
|
42
|
+
import { useData, useRoute } from 'vitepress'
|
|
43
|
+
import { computed, ref, watch } from 'vue'
|
|
44
|
+
import { ElAffix, ElButton } from 'element-plus'
|
|
45
|
+
import { Comment } from '@element-plus/icons-vue'
|
|
46
|
+
import { useGiscusConfig } from '../composables/config/blog'
|
|
47
|
+
import { Theme } from '../composables/config/index'
|
|
48
|
+
|
|
49
|
+
const { frontmatter } = useData()
|
|
50
|
+
const showCommnetAffix = ref(true)
|
|
51
|
+
const handleVisibleChange = (v: boolean) => {
|
|
52
|
+
showCommnetAffix.value = v
|
|
53
|
+
}
|
|
54
|
+
const handleScrollToComment = () => {
|
|
55
|
+
document.querySelector('#giscus-comment')?.scrollIntoView({
|
|
56
|
+
behavior: 'smooth',
|
|
57
|
+
block: 'start'
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
const giscusConfig = useGiscusConfig()
|
|
61
|
+
|
|
62
|
+
const commentConfig = computed<Partial<Theme.GiscusConfig>>(() => {
|
|
63
|
+
if (!giscusConfig) {
|
|
64
|
+
return {}
|
|
65
|
+
}
|
|
66
|
+
return giscusConfig
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const show = computed(() => {
|
|
70
|
+
if (frontmatter.value.comment === false) {
|
|
71
|
+
return frontmatter.value.comment
|
|
72
|
+
}
|
|
73
|
+
if (!giscusConfig) {
|
|
74
|
+
return giscusConfig
|
|
75
|
+
}
|
|
76
|
+
return (
|
|
77
|
+
giscusConfig.repo &&
|
|
78
|
+
giscusConfig.repoId &&
|
|
79
|
+
giscusConfig.category &&
|
|
80
|
+
giscusConfig.categoryId
|
|
81
|
+
)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
const isDark = useDark({
|
|
85
|
+
storageKey: 'vitepress-theme-appearance'
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const route = useRoute()
|
|
89
|
+
const showComment = ref(true)
|
|
90
|
+
watch(
|
|
91
|
+
() => route.path,
|
|
92
|
+
() => {
|
|
93
|
+
showComment.value = false
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
showComment.value = true
|
|
96
|
+
}, 100)
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
</script>
|
|
100
|
+
<style scoped lang="scss">
|
|
101
|
+
.comment {
|
|
102
|
+
width: 100%;
|
|
103
|
+
text-align: center;
|
|
104
|
+
padding: 40px 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.hidden {
|
|
108
|
+
opacity: 0;
|
|
109
|
+
pointer-events: none;
|
|
110
|
+
}
|
|
111
|
+
.comment-btn {
|
|
112
|
+
:deep(.el-affix--fixed) {
|
|
113
|
+
text-align: right;
|
|
114
|
+
.el-button {
|
|
115
|
+
position: relative;
|
|
116
|
+
right: -100px;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@media screen and (max-width: 1200px) {
|
|
122
|
+
.comment-btn {
|
|
123
|
+
:deep(.el-affix--fixed) {
|
|
124
|
+
opacity: 0.7;
|
|
125
|
+
.el-button {
|
|
126
|
+
position: static;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
</style>
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="card friend-wrapper" v-if="friend?.length">
|
|
3
|
+
<!-- 头部 -->
|
|
4
|
+
<div class="card-header">
|
|
5
|
+
<span class="title">🤝 友情链接</span>
|
|
6
|
+
</div>
|
|
7
|
+
<!-- 文章列表 -->
|
|
8
|
+
<ol class="friend-list">
|
|
9
|
+
<li v-for="v in friend" :key="v.nickname">
|
|
10
|
+
<a :href="v.url" target="_blank">
|
|
11
|
+
<el-avatar :size="50" :src="v.avatar" />
|
|
12
|
+
<div>
|
|
13
|
+
<span class="nickname">{{ v.nickname }}</span>
|
|
14
|
+
<p class="des">{{ v.des }}</p>
|
|
15
|
+
</div>
|
|
16
|
+
</a>
|
|
17
|
+
</li>
|
|
18
|
+
</ol>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script lang="ts" setup>
|
|
23
|
+
import { ElAvatar } from 'element-plus'
|
|
24
|
+
import { useBlogConfig } from '../composables/config/blog'
|
|
25
|
+
|
|
26
|
+
const { friend } = useBlogConfig()
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<style lang="scss" scoped>
|
|
30
|
+
.card {
|
|
31
|
+
position: relative;
|
|
32
|
+
margin: 0 auto 10px;
|
|
33
|
+
padding: 10px;
|
|
34
|
+
width: 100%;
|
|
35
|
+
overflow: hidden;
|
|
36
|
+
border-radius: 0.25rem;
|
|
37
|
+
box-shadow: var(--box-shadow);
|
|
38
|
+
box-sizing: border-box;
|
|
39
|
+
transition: all 0.3s;
|
|
40
|
+
background-color: rgba(var(--bg-gradient));
|
|
41
|
+
display: flex;
|
|
42
|
+
|
|
43
|
+
&:hover {
|
|
44
|
+
box-shadow: var(--box-shadow-hover);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.card-header {
|
|
49
|
+
display: flex;
|
|
50
|
+
width: 100%;
|
|
51
|
+
justify-content: space-between;
|
|
52
|
+
align-items: center;
|
|
53
|
+
|
|
54
|
+
.title {
|
|
55
|
+
font-size: 12px;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.friend-wrapper {
|
|
60
|
+
flex-direction: column;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.friend-list {
|
|
64
|
+
display: flex;
|
|
65
|
+
flex-direction: column;
|
|
66
|
+
list-style: none;
|
|
67
|
+
margin: 0;
|
|
68
|
+
padding: 0 10px 0 0px;
|
|
69
|
+
width: 100%;
|
|
70
|
+
|
|
71
|
+
li {
|
|
72
|
+
padding: 6px;
|
|
73
|
+
margin-top: 10px;
|
|
74
|
+
.el-avatar {
|
|
75
|
+
min-width: 50px;
|
|
76
|
+
}
|
|
77
|
+
a {
|
|
78
|
+
display: flex;
|
|
79
|
+
}
|
|
80
|
+
div {
|
|
81
|
+
padding-left: 10px;
|
|
82
|
+
}
|
|
83
|
+
.nickname {
|
|
84
|
+
font-size: 16px;
|
|
85
|
+
font-weight: 450;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.des {
|
|
89
|
+
color: var(--vp-c-text-2);
|
|
90
|
+
font-size: 14px;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
</style>
|
|
@@ -0,0 +1,105 @@
|
|
|
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
|
+
<script setup lang="ts">
|
|
13
|
+
import { computed, ref } from 'vue'
|
|
14
|
+
import { useData } from 'vitepress'
|
|
15
|
+
import { useHomeConfig, useBlogConfig } from '../composables/config/blog'
|
|
16
|
+
|
|
17
|
+
const { site, frontmatter } = useData()
|
|
18
|
+
const { home } = useBlogConfig()
|
|
19
|
+
|
|
20
|
+
const name = computed(
|
|
21
|
+
() => (frontmatter.value.blog?.name ?? site.value.title) || home?.name || ''
|
|
22
|
+
)
|
|
23
|
+
const motto = computed(() => frontmatter.value.blog?.motto || home?.motto || '')
|
|
24
|
+
const initInspiring = ref<string>(
|
|
25
|
+
frontmatter.value.blog?.inspiring || home?.inspiring || ''
|
|
26
|
+
)
|
|
27
|
+
const inspiring = computed({
|
|
28
|
+
get() {
|
|
29
|
+
return initInspiring.value
|
|
30
|
+
},
|
|
31
|
+
set(newValue) {
|
|
32
|
+
initInspiring.value = newValue
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const homeConfig = useHomeConfig()
|
|
37
|
+
|
|
38
|
+
const changeSlogan = async () => {
|
|
39
|
+
if (typeof homeConfig?.handleChangeSlogan !== 'function') {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
const newSlogan = await homeConfig.handleChangeSlogan(inspiring.value)
|
|
43
|
+
if (typeof newSlogan !== 'string' || !newSlogan.trim()) {
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 重新渲染数据,同时触发动画
|
|
48
|
+
inspiring.value = ''
|
|
49
|
+
setTimeout(async () => {
|
|
50
|
+
inspiring.value = newSlogan
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
</script>
|
|
54
|
+
<style lang="scss" scoped>
|
|
55
|
+
h1 {
|
|
56
|
+
text-align: center;
|
|
57
|
+
.name {
|
|
58
|
+
transition: all 0.25s ease-in-out 0.04s;
|
|
59
|
+
transform: translateY(0px);
|
|
60
|
+
opacity: 1;
|
|
61
|
+
font-weight: bold;
|
|
62
|
+
margin: 0 auto;
|
|
63
|
+
font-size: 36px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.motto {
|
|
67
|
+
position: relative;
|
|
68
|
+
bottom: 0px;
|
|
69
|
+
font-size: 14px;
|
|
70
|
+
margin-left: 10px;
|
|
71
|
+
|
|
72
|
+
&::before {
|
|
73
|
+
content: '- ';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@media screen and (max-width: 500px) {
|
|
79
|
+
.motto {
|
|
80
|
+
display: none;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
@keyframes fade-in {
|
|
84
|
+
0% {
|
|
85
|
+
opacity: 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
100% {
|
|
89
|
+
opacity: 1;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.inspiring-wrapper {
|
|
94
|
+
margin: 16px 0;
|
|
95
|
+
height: 24px;
|
|
96
|
+
width: auto;
|
|
97
|
+
h2 {
|
|
98
|
+
animation: fade-in 0.5s ease-in-out;
|
|
99
|
+
cursor: pointer;
|
|
100
|
+
text-align: center;
|
|
101
|
+
font-size: 20px;
|
|
102
|
+
line-height: 1.6;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
</style>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import BlogHomeOverview from './BlogHomeOverview.vue'
|
|
3
|
+
import BlogHotArticle from './BlogHotArticle.vue'
|
|
4
|
+
import BlogHomeTags from './BlogHomeTags.vue'
|
|
5
|
+
import BlogFriendLink from './BlogFriendLink.vue'
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<div class="blog-info">
|
|
10
|
+
<!-- 统计数据,日后支持,点击筛选出左侧的数据 -->
|
|
11
|
+
<BlogHomeOverview />
|
|
12
|
+
|
|
13
|
+
<!-- 置顶的一些文章 -->
|
|
14
|
+
<BlogHotArticle />
|
|
15
|
+
|
|
16
|
+
<!-- 友链 -->
|
|
17
|
+
<BlogFriendLink />
|
|
18
|
+
|
|
19
|
+
<!-- 标签 -->
|
|
20
|
+
<BlogHomeTags />
|
|
21
|
+
</div>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<style lang="scss" scoped>
|
|
25
|
+
.blog-info {
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-direction: column;
|
|
28
|
+
min-width: 240px;
|
|
29
|
+
position: relative;
|
|
30
|
+
box-sizing: border-box;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@media screen and (min-width: 767px) {
|
|
34
|
+
.blog-info {
|
|
35
|
+
max-width: 300px;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
</style>
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="card overview-data">
|
|
3
|
+
<div class="overview-item">
|
|
4
|
+
<span class="count">{{ notHiddenArticles.length }}</span>
|
|
5
|
+
<span class="label">博客文章</span>
|
|
6
|
+
</div>
|
|
7
|
+
<div class="split"></div>
|
|
8
|
+
<div class="overview-item">
|
|
9
|
+
<span class="count">+{{ currentMonth?.length }}</span>
|
|
10
|
+
<span class="label">本月更新</span>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="split"></div>
|
|
13
|
+
<div class="overview-item">
|
|
14
|
+
<span class="count">+{{ currentWeek?.length }}</span>
|
|
15
|
+
<span class="label">本周更新</span>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<script lang="ts" setup>
|
|
21
|
+
import { computed } from 'vue'
|
|
22
|
+
import { isCurrentWeek } from '../utils'
|
|
23
|
+
import { useArticles } from '../composables/config/blog'
|
|
24
|
+
|
|
25
|
+
const docs = useArticles()
|
|
26
|
+
const notHiddenArticles = computed(() => {
|
|
27
|
+
return docs.value.filter((v) => !v.meta.hidden)
|
|
28
|
+
})
|
|
29
|
+
const nowMonth = new Date().getMonth()
|
|
30
|
+
const nowYear = new Date().getFullYear()
|
|
31
|
+
const currentMonth = computed(() => {
|
|
32
|
+
return notHiddenArticles.value.filter((v) => {
|
|
33
|
+
const pubDate = new Date(v.meta?.date)
|
|
34
|
+
return pubDate?.getMonth() === nowMonth && pubDate.getFullYear() === nowYear
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const currentWeek = computed(() => {
|
|
39
|
+
return notHiddenArticles.value.filter((v) => {
|
|
40
|
+
const pubDate = new Date(v.meta?.date)
|
|
41
|
+
return isCurrentWeek(pubDate)
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<style lang="scss" scoped>
|
|
47
|
+
.card {
|
|
48
|
+
position: relative;
|
|
49
|
+
margin: 0 auto 10px;
|
|
50
|
+
padding: 10px;
|
|
51
|
+
width: 100%;
|
|
52
|
+
overflow: hidden;
|
|
53
|
+
border-radius: 0.25rem;
|
|
54
|
+
box-shadow: var(--box-shadow);
|
|
55
|
+
box-sizing: border-box;
|
|
56
|
+
transition: all 0.3s;
|
|
57
|
+
background-color: rgba(var(--bg-gradient));
|
|
58
|
+
display: flex;
|
|
59
|
+
|
|
60
|
+
&:hover {
|
|
61
|
+
box-shadow: var(--box-shadow-hover);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.overview-data {
|
|
66
|
+
width: 100%;
|
|
67
|
+
display: flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
justify-content: space-around;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.split {
|
|
73
|
+
width: 1px;
|
|
74
|
+
opacity: 0.8;
|
|
75
|
+
height: 10px;
|
|
76
|
+
background-color: var(--badge-font-color);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.overview-item {
|
|
80
|
+
display: flex;
|
|
81
|
+
flex-direction: column;
|
|
82
|
+
justify-content: center;
|
|
83
|
+
align-items: center;
|
|
84
|
+
position: relative;
|
|
85
|
+
margin: 0 10px;
|
|
86
|
+
|
|
87
|
+
.count {
|
|
88
|
+
font-size: 18px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.label {
|
|
92
|
+
margin-top: 6px;
|
|
93
|
+
font-size: 12px;
|
|
94
|
+
color: var(--description-font-color);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
</style>
|