本系列教程共十篇
页脚添加计时功能
预览效果

新建文件
以下是新建文件部分
- 新增
components\SakuraFooter.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
| <script lang="ts" setup> import type { Pkg } from 'valaxy' import { useConfig } from 'valaxy' import { isClient } from '@vueuse/core' import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
interface FooterConfig { icp?: string powered?: boolean runtimeSince?: string }
interface RuntimeParts { days: number hours: number minutes: number seconds: number }
const props = defineProps<{ footer?: FooterConfig pkg?: Pkg }>()
const config = useConfig()
const footer = computed(() => { if (props.footer) return props.footer
return (config.value?.themeConfig as { footer?: FooterConfig } | undefined)?.footer ?? {} })
const runtime = ref<RuntimeParts | null>(null)
let timer: ReturnType<typeof setInterval> | undefined
function parseStartTime(since: string) { const trimmed = since.trim()
if (/^\d{4}-\d{2}-\d{2}$/.test(trimmed)) { const [year, month, day] = trimmed.split('-').map(Number) return new Date(year, month - 1, day, 0, 0, 0, 0) }
const start = new Date(trimmed) if (Number.isNaN(start.getTime())) return null
return start }
function updateRuntime() { const since = footer.value.runtimeSince if (!since) { runtime.value = null return }
const start = parseStartTime(since) if (!start) { runtime.value = null return }
let diffMs = Date.now() - start.getTime() if (diffMs < 0) { runtime.value = null return }
const days = Math.floor(diffMs / 86_400_000) diffMs -= days * 86_400_000
const hours = Math.floor(diffMs / 3_600_000) diffMs -= hours * 3_600_000
const minutes = Math.floor(diffMs / 60_000) diffMs -= minutes * 60_000
const seconds = Math.floor(diffMs / 1000)
runtime.value = { days, hours, minutes, seconds } }
function stopTimer() { if (timer !== undefined) { clearInterval(timer) timer = undefined } }
function startTimer() { updateRuntime() stopTimer()
if (isClient) timer = setInterval(updateRuntime, 1000) }
watch(() => footer.value.runtimeSince, startTimer)
onMounted(startTimer) onUnmounted(stopTimer) </script>
<template> <footer class="sakura-footer h-$sakura-footer-height" text="center sm" style="color:var(--va-c-text-light)"> <div v-if="footer.icp" class="icp" p="y-2" v-html="footer.icp" />
<SakuraCopyright />
<p v-if="runtime" class="sakura-footer-runtime"> 本站已经流畅运行 <span class="sakura-footer-runtime__num">{{ runtime.days }}</span>天 <span class="sakura-footer-runtime__num">{{ runtime.hours }}</span>小时 <span class="sakura-footer-runtime__num">{{ runtime.minutes }}</span>分钟 <span class="sakura-footer-runtime__num">{{ runtime.seconds }}</span>秒 </p>
<SakuraPowered v-if="footer.powered" />
<slot /> </footer> </template>
<style lang="scss" scoped> .sakura-footer-runtime { margin: 0; padding: 4px 0 8px; font-size: 0.875rem; line-height: 1.6; color: var(--va-c-text-light); }
.sakura-footer-runtime__num { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--sakura-color-primary, #fe9500); } </style>
|
修改文件
以下是修改文件部分
- 在
valaxy.config.ts文件footer部分(在(themeconfig内部)增加以下代码
1
| runtimeSince: '2026-6-01',
|
最终效果
1 2 3 4 5
| footer: {
runtimeSince: '2026-6-01',
},
|
这是控制计时起始时间的
搜索问题修复
前景提要
这个没有预览图哈,不知道你们遇到没有,反正主播遇到了,我使用搜索功能的时候老搜不到东西,很奇怪,顺便说一下怎么开启搜索功能,你直接点那个按钮他是有一个空壳哈,得先新建搜索页面pages\search新建文件index.md
在里面写:
1 2 3 4 5 6 7
| --- layout: search title: 搜索 icon: i-ri-search-line cover: /path/to/your/cover-image.jpg comment: false ---
|
然后还要开启搜索支持:
本地搜索(基于 fuse.js)
Valaxy 内置了基于 fuse.js 的离线搜索(须预先通过 valaxy fuse 构建生成本地缓存)。
valaxy fuse 默认将 fuse 生成在 public 目录下,并在 .gitignore 中添加 public/valaxy-fuse-list.json 忽略。 在执行 valaxy build 之前,会自动执行 valaxy fuse。
如果你想要使用全文搜索,需要进行如下设置 :
编辑site.config.ts文件,以下给的完整代码,注意哦
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| export default defineSiteConfig({ search: { enable: true, type: 'fuse', }, fuse: {
// pattern: 'pages/**/*.md', options: { keys: ['title', 'tags', 'categories', 'excerpt', 'content'],
// threshold: 0.6,
ignoreLocation: true, }, }, })
|
还需要在文件中设置启用搜素:
编辑site.config.ts文件,这里也是完整代码哦
1 2 3 4 5 6 7 8 9
| import { defineSiteConfig } from 'valaxy'
export default defineSiteConfig({ search: { enable: true, provider: 'fuse', }, })
|
还需要在你的 package.json 中添加 fuse 生成脚本
编辑package.json文件
主要加这两行:
1 2
| "build": "npm run build:ssg", "fuse": "valaxy fuse",
|
添加位置看下方完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| { "name": "yun-demo", "valaxy": { "theme": "yun" }, "scripts": { "build": "npm run build:ssg", "build:ssg": "valaxy build --ssg", "fuse": "valaxy fuse", "rss": "valaxy rss" }, "dependencies": { "valaxy": "latest", "valaxy-theme-yun": "latest" } }
|
这样就算启用搜索了,但是我用起来有bug,这里给出修复方案
修复方案
这里很简单,新建一个文件即可
- 新增
components\layouts\SakuraSearchLayout.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
| <script lang="ts" setup> import { useFuseSearch } from 'valaxy' import { ref, watch } from 'vue' import { useRoute } from 'vue-router' import { isClient } from '@vueuse/core' import { nextTick } from 'vue'
const input = ref('')
const { results, fetchFuseListData } = useFuseSearch(input) const route = useRoute()
watch( () => route.query.q as string, async (query) => { if (isClient) { await fetchFuseListData() await nextTick() }
input.value = query || '' }, { immediate: true } ) </script>
<template> <RouterView v-slot="{ Component }"> <component :is="Component"> <template #main-content> <slot name="content"> <div class="sakura-search mt-20"> <header class="page-header"> <h1 class="page-title"> <template v-if="results.length > 0"> 搜索结果: {{ input }} </template> <template v-else> 没有找到任何东西! </template> </h1> </header> <div v-if="results.length > 0" overflow="auto" flex="~" w="full"> <div class="sakura-search-result post post-list" flex="~ col" w="full"> <div v-for="result in results" :key="result.item.title" class="post-entry" flex="~"> <AppLink :to="result.item.link"> <div class="feature"> <div class="flex-center overlay"> <div i-fa-file-text-o /> </div> <SakuraImageCard :src="result.item?.cover" class="h-full rounded-full" /> </div> </AppLink> <div class="ml-9 flex-grow"> <div flex="~" class="items-center justify-between"> <h3 class="sakura-search-result-title entry-title"> <AppLink :to="result.item.link"> {{ result.item.title }} </AppLink> </h3>
<div class="p-time flex items-center"> <span i-mdi-access-time class="mr-1 inline-flex" /> 发布于 {{ result.item.date }} </div> </div>
<p class="sakura-search-result-excerpt"> {{ result.item.excerpt }} </p>
<div class="post-more"> <AppLink :to="result.item.link"> <SakuraDots class="float-right mt-10px" /> </AppLink> </div> <hr w="1/3" class="mx-auto mb-62px mt-69px" border="~ $sakura-color-divider"> </div> </div> </div> </div> </div> </slot> </template> </component> </RouterView> </template>
<style lang="scss" scoped> .sakura-search { padding-top: 4rem; margin-top: var(--sakura-navbar-height); margin-right: auto; margin-left: auto; pointer-events: auto; transition: color 0.2s ease; width: 90%; max-width: 800px;
&-input { background: transparent; color: var(--sakura-color-text); font-size: 1.5rem; border-radius: 3rem; padding: 1rem 1.5rem; border: 1px solid var(--sakura-color-border); box-sizing: border-box; font-weight: 900; text-align: center; transition: all 0.2s;
&:focus { border-color: var(--sakura-color-primary); } }
&-result-item { color: var(--sakura-color-text); cursor: pointer;
&:hover { color: var(--sakura-color-primary); } }
.page-header { position: relative; text-align: center; margin-bottom: 50px;
.page-title { font-size: 20px; font-weight: 400; border: 1px dashed var(--sakura-color-divider); padding: 10px 15px; color: var(--sakura-color-text); margin-bottom: 30px; } }
.entry-title { a { color: var(--sakura-color-text-deep); font-size: 20px; font-weight: 400; line-height: 50px; font-family: 'Noto Serif SC', 'Source Han Serif SC', 'Source Han Serif', source-han-serif-sc, 'PT Serif', 'SongTi SC', 'MicroSoft Yahei', Georgia, serif;
&:hover { color: var(--sakura-color-primary); } } }
.sakura-search-result-excerpt { font-size: 15px; color: var(--sakura-color-text); letter-spacing: 0; line-height: 30px; }
.p-time { font-size: 12px; color: var(--sakura-color-text); letter-spacing: 0; }
.post-more { font-size: 25px; color: var(--sakura-color-text); }
.feature { border-radius: 50%; padding: 2px; position: relative; border: 1px solid var(--sakura-color-divider); height: 100px; width: 100px; overflow: hidden; }
.overlay { position: absolute; inset: 0; z-index: 1; background-color: orange; opacity: 0; transition: opacity 0.3s ease-out; pointer-events: none;
div { font-size: 25px; color: oklch(100% 0 0); line-height: 94px; } }
.feature:hover .overlay { opacity: 1; pointer-events: auto; } } </style>
|
好啦,本次教程就到这了