快捷菜单
常用功能一站直达
更多功能请点顶栏「快捷菜单」
date: 2025-09-25 18:07:27 title: 给Teek配置go壁纸api categories:

[[toc]]
[toc]
📌需求:给宇宙最美博客-Teek 接入一个壁纸api
📌具体描述:
目前,宇宙最美博客-Teek的壁纸 已具备 每隔8s随机从壁纸库 显示一张新壁纸功能了,且壁纸有动态放大缩小效果,很美观👏。
此时,我想实现一个 壁纸api 的需求:
我们对用户只需要暴漏一个域名地址,例如imgapi.onedayxyy.cn,然后在浏览器打开这个域名后,就会出现一张壁纸,每刷新一次,就会随机出现一张新壁纸。
那么该如何接入Teek使用呢?
方案1:Teek修改代码,让自己适配这个壁纸api,博客自己 每隔8s就会 强制 请求壁纸api ,让其显示一张新壁纸
方案2:壁纸api 自己实现“每隔8s随机从 壁纸api 显示一张新壁纸”功能,但teek接入后,也需要能正常每隔8s随机从 壁纸api 显示一张新壁纸才行;
这样做的好处就是:
壁纸api 源源不断地新增漂亮的风景照/好看的妹纸照片。我们无需修改teek的代码,而我们的网站就可以出现新增的漂亮照片;说明:
1不管怎么实现,越简单越好,能实现就好。
2
3首先,壁纸api自身的2个功能不能变:
4
5例如壁纸api
61. imgapi.onedayxyy.cn在自己浏览器打开,会显示一张图片,刷新一次,会切换到另一张图片
72.间隔数s后,会随机切换到下一张图片
8
9然后在次基础上,不管是改teek的代码来适配壁纸api,还是改壁纸api的代码来适配teek,或者两个都改,由开发者自己选择最优解。经过Teek群里多次讨论,余温-go大佬 终于把这个功能给开发好了。💖💖💖
余温-go大佬 把这个 go壁纸api 项目开发完成了,那么接下来就出一个手把手文章吧。😜

2025年9月25日测试。
自己在《Teek-one开源库:https://cnb.cool/onedayxyy/vitepress-theme-teek-one-public》的6f82dbb03134b88284d5e15bd8c2dce72a202589 版本上测试成功的。(后续版本已经集成这个代码了。)
Teek@1.4.6-2025.8.31
(1)编辑docs/.vitepress/ConfigHyde/Wallaper.ts文件,将其全部替换为如下内容:
1// 首页壁纸 - 动态获取本地图片服务器的图片
2
3// 壁纸服务配置 - 统一配置服务地址,一处修改全局生效
4export const WALLPAPER_SERVICE_CONFIG = {
5 baseUrl: 'https://imgapi.onedayxyy.cn',
6 // baseUrl: 'https://imgapi.onedayxyy.cn',
7 apiEndpoint: '/api/images',
8 get fullUrl() {
9 return `${this.baseUrl}${this.apiEndpoint}`
10 }
11}
12
13// 备用图片列表(当本地服务不可用时使用)
14const fallbackImages = [
15 "https://img.onedayxyy.cn/images/Teek/TeekBg/1.avif",
16 // "https://img.onedayxyy.cn/images/Teek/TeekBg/2.avif",
17 // "https://img.onedayxyy.cn/images/Teek/TeekBg/3.avif",
18 // "https://img.onedayxyy.cn/images/Teek/TeekBg/4.avif",
19 // "https://img.onedayxyy.cn/images/Teek/TeekBg/5.avif",
20 // "https://img.onedayxyy.cn/images/Teek/TeekBg/6.avif",
21 // "https://img.onedayxyy.cn/images/Teek/TeekBg/7.avif",
22 // "https://img.onedayxyy.cn/images/Teek/TeekBg/8.avif",
23 // "https://img.onedayxyy.cn/images/Teek/TeekBg/9.avif",
24 // "https://img.onedayxyy.cn/images/Teek/TeekBg/10.avif",
25 // "https://img.onedayxyy.cn/images/Teek/TeekBg/11.avif",
26 // "https://img.onedayxyy.cn/images/Teek/TeekBg/12.avif",
27 // "https://img.onedayxyy.cn/images/Teek/TeekBg/13.avif",
28 // "https://img.onedayxyy.cn/images/Teek/TeekBg/14.avif",
29 // "https://img.onedayxyy.cn/images/Teek/TeekBg/15.avif",
30 // "https://img.onedayxyy.cn/images/Teek/TeekBg/16.avif",
31 // "https://img.onedayxyy.cn/images/Teek/TeekBg/17.avif",
32 // "https://img.onedayxyy.cn/images/Teek/TeekBg/18.avif",
33 // "https://img.onedayxyy.cn/images/Teek/TeekBg/19.avif",
34];
35// 动态获取图片列表的函数
36async function fetchDynamicWallpapers(): Promise<string[]> {
37 try {
38 // 使用统一配置的图片服务API
39 const response = await fetch(WALLPAPER_SERVICE_CONFIG.fullUrl, {
40 method: 'GET',
41 headers: {
42 'Accept': 'application/json',
43 },
44 });
45
46 if (!response.ok) {
47 throw new Error(`HTTP ${response.status}: ${response.statusText}`);
48 }
49
50 const data = await response.json();
51 const images = data.images || [];
52
53 // 将相对路径转换为完整的服务器URL
54 const wallpapers = images.map((imagePath: string) => `${WALLPAPER_SERVICE_CONFIG.baseUrl}${imagePath}`);
55
56 // 如果获取到图片,返回动态图片列表,否则返回备用图片
57 return wallpapers.length > 0 ? wallpapers : wallpapers;
58
59 } catch (error) {
60 console.warn('无法获取动态壁纸,使用备用图片:', error);
61 return fallbackImages;
62 }
63}
64
65// 创建一个Promise来获取壁纸
66let wallpaperPromise: Promise<string[]> | null = null;
67
68// 获取壁纸的函数
69function getWallpapers(): Promise<string[]> {
70 if (!wallpaperPromise) {
71 wallpaperPromise = fetchDynamicWallpapers();
72 }
73 return wallpaperPromise;
74}
75
76// 导出的Wallpaper数组 - 在服务端渲染时使用备用图片,客户端动态加载
77export const Wallpaper: string[] = fallbackImages;
78
79// 导出动态获取函数供主题使用
80export { getWallpapers, fetchDynamicWallpapers };(2)创建docs/.vitepress/theme/components/DynamicWallpaperManager.vue文件:
1<template>
2 <!-- 这是一个无渲染组件,仅用于管理动态壁纸 -->
3</template>
4
5<script setup lang="ts">
6import { onMounted, onUnmounted, nextTick, watch } from 'vue'
7import { useRoute } from 'vitepress'
8import { fetchDynamicWallpapers, WALLPAPER_SERVICE_CONFIG } from '../../ConfigHyde/Wallaper'
9
10// 路由检测 - 只在首页启用动态壁纸
11const route = useRoute()
12let isHomePage = false
13
14// 检查是否为首页
15function checkIsHomePage(): boolean {
16 const path = route.path
17 return path === '/' || path === '/index.html' || path === ''
18}
19
20// 停止所有动态壁纸相关功能
21function stopWallpaperSystem() {
22 if (fetchLibraryIntervalId) {
23 clearInterval(fetchLibraryIntervalId)
24 fetchLibraryIntervalId = null
25 }
26 if (switchImageIntervalId) {
27 clearInterval(switchImageIntervalId)
28 switchImageIntervalId = null
29 }
30 stopServiceMonitoring()
31
32 // 清理呼吸动画类
33 const backgroundElements = document.querySelectorAll('.wallpaper-breathing')
34 backgroundElements.forEach(element => {
35 element.classList.remove('wallpaper-breathing')
36 })
37
38 console.log('🖼️ 动态壁纸系统已停止(非首页)')
39}
40
41// 启动动态壁纸系统(仅首页)
42function startWallpaperSystem() {
43 if (!isHomePage) return
44
45 console.log('🖼️ 动态壁纸系统启动(首页)')
46 // 壁纸系统启动逻辑将在 onMounted 中调用
47}
48
49// 定义时间间隔配置
50const FETCH_LIBRARY_INTERVAL = 60 * 1000 // 60秒请求一次图集服务器获取图库
51const SWITCH_IMAGE_INTERVAL = 10 * 1000 // 10秒从图库中随机选择一张图片展示
52const SERVICE_CHECK_INTERVAL = 15 * 1000 // 15秒检测一次服务状态(当使用备用图片时)
53
54let fetchLibraryIntervalId: number | null = null // 图库请求定时器
55let switchImageIntervalId: number | null = null // 图片切换定时器
56let serviceCheckIntervalId: number | null = null // 服务检测定时器
57let currentImages: string[] = [] // 当前图库
58let currentDisplayImage: string = '' // 当前展示的图片
59let isUsingFallback: boolean = false // 是否正在使用备用图片
60let lastSuccessfulFetch: number = 0 // 上次成功获取图库的时间
61
62// 双图层状态管理
63let currentActiveLayer: 'A' | 'B' = 'A' // 当前活动图层
64let isTransitioning: boolean = false // 是否正在切换中
65let layerHistory: Array<{ layer: 'A' | 'B', image: string, timestamp: number }> = [] // 图层历史记录
66let pendingSwitchQueue: Array<{ image: string, timestamp: number }> = [] // 待处理的切换队列
67
68// 缓存相关
69const CACHE_KEY = 'dynamic-wallpaper-cache'
70const LAST_IMAGE_KEY = 'last-wallpaper-image'
71
72// 保存图库到缓存
73function saveImagesToCache(images: string[]) {
74 try {
75 // 检查是否在客户端环境
76 if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
77 return
78 }
79 localStorage.setItem(CACHE_KEY, JSON.stringify(images))
80 } catch (error) {
81 console.warn('保存图库缓存失败:', error)
82 }
83}
84
85// 从缓存加载图库
86function loadImagesFromCache(): string[] {
87 try {
88 // 检查是否在客户端环境
89 if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
90 return []
91 }
92 const cached = localStorage.getItem(CACHE_KEY)
93 return cached ? JSON.parse(cached) : []
94 } catch (error) {
95 console.warn('加载图库缓存失败:', error)
96 return []
97 }
98}
99
100// 保存最后显示的图片
101function saveLastImage(imageSrc: string) {
102 try {
103 // 检查是否在客户端环境
104 if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
105 return
106 }
107 localStorage.setItem(LAST_IMAGE_KEY, imageSrc)
108 } catch (error) {
109 console.warn('保存最后图片失败:', error)
110 }
111}
112
113// 获取最后显示的图片
114function getLastImage(): string | null {
115 try {
116 // 检查是否在客户端环境
117 if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
118 return null
119 }
120 return localStorage.getItem(LAST_IMAGE_KEY)
121 } catch (error) {
122 console.warn('获取最后图片失败:', error)
123 return null
124 }
125}
126
127// 预加载图片以避免加载闪烁,检测图片是否可用
128function preloadImage(src: string): Promise<boolean> {
129 return new Promise((resolve) => {
130 const img = new Image()
131
132 // 设置3秒超时
133 const timeout = setTimeout(() => {
134 img.onload = null
135 img.onerror = null
136 resolve(false)
137 }, 3000)
138
139 img.onload = () => {
140 clearTimeout(timeout)
141 resolve(true) // 加载成功
142 }
143 img.onerror = () => {
144 clearTimeout(timeout)
145 resolve(false) // 加载失败
146 }
147 img.src = src
148 })
149}
150
151// 获取图层状态信息
152function getLayerStatus(): { currentLayer: 'A' | 'B', isTransitioning: boolean, history: typeof layerHistory } {
153 return {
154 currentLayer: currentActiveLayer,
155 isTransitioning,
156 history: [...layerHistory] // 返回副本
157 }
158}
159
160// 记录图层状态到历史
161function recordLayerState(layer: 'A' | 'B', image: string) {
162 const now = Date.now()
163 layerHistory.push({ layer, image, timestamp: now })
164
165 // 保持历史记录在合理范围内(最多保留10条)
166 if (layerHistory.length > 10) {
167 layerHistory.shift()
168 }
169
170 console.log('📝 图层状态记录:', { layer, image, timestamp: now })
171}
172
173// 清理过期的待处理队列
174function cleanPendingQueue() {
175 const now = Date.now()
176 const maxAge = 5000 // 5秒过期
177
178 const beforeLength = pendingSwitchQueue.length
179 pendingSwitchQueue = pendingSwitchQueue.filter(item => now - item.timestamp < maxAge)
180
181 if (beforeLength !== pendingSwitchQueue.length) {
182 console.log('🧹 清理过期的待处理队列:', beforeLength - pendingSwitchQueue.length, '个项目')
183 }
184}
185
186// 处理待处理的切换队列
187async function processPendingQueue() {
188 if (isTransitioning || pendingSwitchQueue.length === 0) {
189 return
190 }
191
192 // 清理过期项目
193 cleanPendingQueue()
194
195 // 获取最早的待处理项目
196 const nextSwitch = pendingSwitchQueue.shift()
197 if (nextSwitch) {
198 console.log('📋 处理待切换队列:', nextSwitch.image)
199 await updateBannerBackgroundWithCSS(nextSwitch.image)
200 }
201}
202
203// 队列化切换请求
204function queueSwitch(imageSrc: string) {
205 const now = Date.now()
206
207 // 如果队列中已有相同图片,移除旧的
208 pendingSwitchQueue = pendingSwitchQueue.filter(item => item.image !== imageSrc)
209
210 // 添加新的请求
211 pendingSwitchQueue.push({ image: imageSrc, timestamp: now })
212
213 console.log('📥 切换请求已加入队列:', imageSrc, '队列长度:', pendingSwitchQueue.length)
214
215 // 尝试立即处理队列
216 setTimeout(processPendingQueue, 100)
217}
218
219// 添加过渡动画样式
220function addTransitionStyles(element: HTMLElement) {
221 if (!element.style.transition || !element.style.transition.includes('opacity')) {
222 element.style.transition = element.style.transition
223 ? `${element.style.transition}, opacity 0.3s ease-in-out`
224 : 'opacity 0.3s ease-in-out'
225 }
226}
227
228// 简化的内容层稳定性函数(CSS变量方案下不再需要复杂的DOM操作)
229function ensureBannerContentStability() {
230 // 仅确保banner容器有正确的基本设置
231 const bannerEl = document.querySelector('.tk-banner') as HTMLElement
232 if (bannerEl) {
233 bannerEl.style.overflow = 'hidden' // 隐藏背景溢出,避免滚动条
234 bannerEl.style.isolation = 'isolate'
235
236 // 在生产环境中,立即隐藏可能的默认背景图片防止闪现
237 const isProduction = import.meta.env.PROD
238 if (isProduction) {
239 // 立即隐藏所有可能的背景元素,防止默认图片闪现
240 const allBgElements = document.querySelectorAll('.tk-banner__bg, .tk-banner-bg, [class*="banner"][class*="bg"], .banner-bg')
241 allBgElements.forEach(el => {
242 const element = el as HTMLElement
243 element.style.opacity = '0'
244 element.style.visibility = 'hidden'
245 element.style.zIndex = '-1000'
246 })
247 }
248
249 // 彻底清理可能冲突的旧背景元素样式
250 const oldBgElements = bannerEl.querySelectorAll('.tk-banner__bg, .tk-banner-bg, [class*="banner"][class*="bg"]')
251 oldBgElements.forEach(el => {
252 const element = el as HTMLElement
253 // 完全隐藏旧的背景元素避免格子问题
254 element.style.display = 'none'
255 element.style.backgroundImage = 'none'
256 element.style.background = 'none'
257 element.style.opacity = '0'
258 element.style.zIndex = '-1000'
259 })
260
261 // 清理banner本身可能的背景样式
262 bannerEl.style.backgroundImage = 'none'
263 bannerEl.style.background = 'transparent'
264 }
265}
266
267// CSS变量方案下,不再需要复杂的保护和调试函数
268// 保留简单的调试函数用于开发
269function debugBannerState() {
270 const bannerEl = document.querySelector('.tk-banner') as HTMLElement
271 if (bannerEl) {
272 const bgImage = bannerEl.style.getPropertyValue('--banner-bg-image')
273 const bgOpacity = bannerEl.style.getPropertyValue('--banner-bg-opacity')
274 console.log('🔍 Banner状态:', { bgImage, bgOpacity })
275 }
276}
277
278// 双图层初始化Banner背景
279function initBannerBackground(imageSrc: string): boolean {
280 const bannerEl = document.querySelector('.tk-banner') as HTMLElement
281
282 if (bannerEl) {
283 // 立即隐藏可能的默认Banner背景,防止闪现
284 const oldBgElements = bannerEl.querySelectorAll('.tk-banner__bg, .tk-banner-bg, [class*="banner"][class*="bg"]')
285 oldBgElements.forEach(el => {
286 const element = el as HTMLElement
287 element.style.opacity = '0'
288 element.style.visibility = 'hidden'
289 })
290
291 // 使用主题背景色避免蓝色闪烁,与blogger-fix.scss保持一致
292 bannerEl.style.background = 'var(--vp-c-bg)'
293 bannerEl.style.backgroundColor = 'var(--vp-c-bg)'
294
295 // 立即设置图层A并显示,避免透明状态
296 bannerEl.style.setProperty('--layer-a-bg-image', `url("${imageSrc}")`)
297 bannerEl.style.setProperty('--layer-a-opacity', '1') // 立即显示,不从0开始
298 bannerEl.style.setProperty('--layer-b-opacity', '0')
299
300 // 立即显示图层A类
301 bannerEl.classList.remove('has-layer-b', 'dual-layer')
302 bannerEl.classList.add('has-layer-a')
303 currentActiveLayer = 'A'
304
305 // 预加载图片用于验证
306 const img = new Image()
307 img.onload = () => {
308 console.log('🎯 双图层初始化 - 图片验证成功:', imageSrc)
309 // 图片加载成功后添加背景加载完成的类,移除预设背景色
310 setTimeout(() => {
311 bannerEl.classList.add('background-loaded')
312 }, 300) // 确保图片已显示后再移除背景色
313 }
314 img.onerror = () => {
315 console.warn('❌ 双图层初始化图片验证失败,保持预设背景:', imageSrc)
316 // 图片加载失败时保持预设背景色,不添加background-loaded类
317 }
318
319 console.log('🔄 双图层系统 - 立即显示背景并验证图片:', imageSrc)
320 img.src = imageSrc
321 return true
322 }
323
324 console.warn('❌ 未找到.tk-banner元素')
325 return false
326}
327
328// 双图层无缝切换函数 - 解决白屏问题
329async function updateBannerBackgroundWithCSS(imageSrc: string): Promise<boolean> {
330 const bannerEl = document.querySelector('.tk-banner') as HTMLElement
331
332 if (!bannerEl) {
333 console.warn('❌ 未找到.tk-banner元素')
334 return false
335 }
336
337 // 如果正在切换中,将请求加入队列
338 if (isTransitioning) {
339 console.log('⏭️ 正在切换中,将请求加入队列:', imageSrc)
340 queueSwitch(imageSrc)
341 return false
342 }
343
344 try {
345 // 预加载图片确保切换时不会有闪烁
346 const preloadSuccess = await preloadImage(imageSrc)
347
348 if (!preloadSuccess) {
349 console.warn('❌ 双图层切换失败,图片预加载失败:', imageSrc)
350 return false
351 }
352
353 console.log('🔄 双图层无缝切换开始 - 当前活动图层:', currentActiveLayer)
354
355 isTransitioning = true
356 const oldActiveLayer = currentActiveLayer
357 const targetLayer = currentActiveLayer === 'A' ? 'B' : 'A'
358
359 // 记录切换开始状态
360 recordLayerState(oldActiveLayer, currentDisplayImage || '当前图片')
361
362 // 1. 在非活动图层预加载新图片
363 if (targetLayer === 'A') {
364 bannerEl.style.setProperty('--layer-a-bg-image', `url("${imageSrc}")`)
365 bannerEl.style.setProperty('--layer-a-opacity', '0')
366 bannerEl.classList.add('has-layer-a', 'dual-layer') // 启用双图层显示
367 } else {
368 bannerEl.style.setProperty('--layer-b-bg-image', `url("${imageSrc}")`)
369 bannerEl.style.setProperty('--layer-b-opacity', '0')
370 bannerEl.classList.add('has-layer-b', 'dual-layer') // 启用双图层显示
371 }
372
373 // 2. 新图层淡入(此时两个图层都显示,无白屏)
374 await new Promise<void>(resolve => {
375 setTimeout(() => {
376 if (targetLayer === 'A') {
377 bannerEl.style.setProperty('--layer-a-opacity', '1')
378 } else {
379 bannerEl.style.setProperty('--layer-b-opacity', '1')
380 }
381 console.log('🔄 新图层开始淡入:', targetLayer, '透明度设为1')
382 resolve()
383 }, 100) // 短暂延迟确保CSS生效
384 })
385
386 // 3. 等待新图层完全显示后,隐藏旧图层
387 await new Promise<void>(resolve => {
388 // 等待新图层过渡动画完全结束再隐藏旧图层
389 setTimeout(() => {
390 console.log('🔄 新图层已完全可见,开始隐藏旧图层:', oldActiveLayer)
391
392 // 现在开始隐藏旧图层
393 if (oldActiveLayer === 'A') {
394 bannerEl.style.setProperty('--layer-a-opacity', '0')
395 } else {
396 bannerEl.style.setProperty('--layer-b-opacity', '0')
397 }
398
399 // 等待旧图层完全隐藏后再清理类
400 setTimeout(() => {
401 // 移除旧图层的显示类
402 if (oldActiveLayer === 'A') {
403 bannerEl.classList.remove('has-layer-a')
404 } else {
405 bannerEl.classList.remove('has-layer-b')
406 }
407
408 // 4. 切换完成,更新状态
409 currentActiveLayer = targetLayer
410 bannerEl.classList.remove('dual-layer') // 关闭双图层模式
411 bannerEl.classList.add('background-loaded') // 确保移除预设背景
412
413 // 记录新的图层状态
414 recordLayerState(targetLayer, imageSrc)
415
416 isTransitioning = false
417 console.log('✅ 双图层无缝切换完成 - 新活动图层:', currentActiveLayer, '图片:', imageSrc)
418
419 // 保存当前显示的图片到缓存
420 saveLastImage(imageSrc)
421
422 resolve()
423 }, 2050) // 缩短等待时间(2s + 50ms 缓冲)
424 }, 1800) // 缩短新图层等待时间(1.8s)
425 })
426
427 // 处理队列中可能的待处理请求
428 setTimeout(processPendingQueue, 100)
429
430 return true
431
432 } catch (error) {
433 console.error('❌ 双图层切换过程中发生错误:', error)
434 isTransitioning = false
435 return false
436 }
437}
438
439// 确保双图层呼吸动画同步
440function ensureBreathingAnimationSync() {
441 const bannerEl = document.querySelector('.tk-banner') as HTMLElement
442
443 if (bannerEl) {
444 // 通过重置animation触发同步
445 const resetAnimation = () => {
446 bannerEl.style.setProperty('--animation-state', 'paused')
447
448 // 使用requestAnimationFrame确保DOM更新
449 requestAnimationFrame(() => {
450 bannerEl.style.setProperty('--animation-state', 'running')
451 console.log('🔄 双图层呼吸动画已同步')
452 })
453 }
454
455 // 在双图层模式时重置动画确保同步
456 if (bannerEl.classList.contains('dual-layer')) {
457 resetAnimation()
458 }
459 }
460}
461
462// 添加呼吸动画到所有Banner背景元素(向后兼容)
463function addBreathingAnimation() {
464 console.log('💨 呼吸动画已通过CSS伪元素自动启用')
465
466 // 现在呼吸动画主要通过::before和::after伪元素实现
467 // 这个函数保留用于向后兼容旧的背景元素
468 const selectors = [
469 '.tk-banner .tk-banner__bg',
470 '.tk-banner .tk-banner-bg',
471 '.tk-banner [class*="banner"][class*="bg"]',
472 '.banner-bg',
473 '.tk-banner-bg'
474 ]
475
476 selectors.forEach(selector => {
477 const elements = document.querySelectorAll(selector)
478 elements.forEach(element => {
479 if (!element.classList.contains('wallpaper-breathing')) {
480 element.classList.add('wallpaper-breathing')
481 console.log('🌟 添加补充呼吸动画到:', selector)
482 }
483 })
484 })
485
486 // 确保伪元素动画同步
487 ensureBreathingAnimationSync()
488}
489
490// 旧的双图层函数已被CSS变量方案替代
491// 保留用于向后兼容或作为降级方案
492function updateBannerBackground(imageSrc: string): boolean {
493 console.warn('⚠️ 使用了旧的双图层方法,建议使用updateBannerBackgroundWithCSS')
494 // 降级到CSS变量方案
495 return updateBannerBackgroundWithCSS(imageSrc)
496}
497
498// 从当前图库中随机选择一张图片展示(带去重逻辑)
499async function displayRandomImage() {
500 // 检查图库是否为空
501 if (!currentImages || currentImages.length === 0) {
502 console.warn('图库为空,使用备用图片')
503 currentImages = getFallbackImages()
504 isUsingFallback = true
505 }
506
507 let availableImages = currentImages
508
509 // 如果图库中有多张图片,排除当前正在展示的图片
510 if (currentImages.length > 1 && currentDisplayImage) {
511 availableImages = currentImages.filter(img => img !== currentDisplayImage)
512 }
513
514 // 如果过滤后没有图片了,使用全部图片
515 if (availableImages.length === 0) {
516 availableImages = currentImages
517 }
518
519 const randomImg = availableImages[Math.floor(Math.random() * availableImages.length)]
520
521 // 如果选中的图片和当前展示的相同,直接返回(避免不必要的更新)
522 if (randomImg === currentDisplayImage) return
523
524 try {
525 // 预加载图片并检测是否成功
526 const preloadSuccess = await preloadImage(randomImg)
527
528 // 如果图片加载失败且当前使用的是动态图库,切换到备用图片
529 if (!preloadSuccess && !isUsingFallback) {
530 console.warn('🔌 动态图片加载失败,服务可能已停止,切换到备用图片')
531 currentImages = getFallbackImages()
532 isUsingFallback = true
533
534 // 启动服务监控
535 startServiceMonitoring()
536
537 // 重新从备用图库选择图片
538 const fallbackImg = currentImages[Math.floor(Math.random() * currentImages.length)]
539 const fallbackPreloadSuccess = await preloadImage(fallbackImg)
540
541 if (fallbackPreloadSuccess) {
542 currentDisplayImage = fallbackImg
543 updateBannerBackground(fallbackImg)
544 return
545 } else {
546 console.error('备用图片也加载失败')
547 return
548 }
549 } else if (!preloadSuccess) {
550 // 备用图片也加载失败,跳过此次更新
551 console.warn('图片加载失败,跳过此次更新')
552 return
553 }
554
555 // 更新当前展示图片记录
556 currentDisplayImage = randomImg
557
558 // 使用新的CSS变量方法更新Banner背景
559 const success = updateBannerBackgroundWithCSS(randomImg)
560
561 if (!success) {
562 // 如果立即更新失败,等待DOM更新后再试
563 await nextTick()
564 updateBannerBackgroundWithCSS(randomImg)
565 }
566
567 // CSS变量方案下,呼吸动画通过伪元素自动应用,无需手动添加
568 setTimeout(() => {
569 debugBannerState() // 调试新的CSS变量状态
570 }, 100)
571
572 // 设置全局变量供其他组件使用
573 ;(window as any).dynamicWallpapers = currentImages
574
575 // 触发自定义事件
576 window.dispatchEvent(new CustomEvent('wallpaper-updated', {
577 detail: { images: currentImages, currentImage: randomImg, layerStatus: getLayerStatus() }
578 }))
579
580 // 开发环境下的状态检查
581 if (typeof window !== 'undefined' && (window as any).location.hostname === 'localhost') {
582 setTimeout(() => {
583 const layerStatus = getLayerStatus()
584 console.log('🔍 壁纸切换后状态检查:', {
585 currentImage: randomImg,
586 layerStatus,
587 timestamp: new Date().toLocaleTimeString()
588 })
589 }, 2000) // 等待切换完全完成后检查
590 }
591
592 const imageType = isUsingFallback ? '备用' : '动态'
593 console.log(`🎨 已切换${imageType}壁纸:`, randomImg)
594
595 } catch (error) {
596 console.warn('切换壁纸失败:', error)
597 }
598}
599
600
601// 检测图集服务是否可用(快速检测,用于初始加载)
602async function checkServiceAvailability(): Promise<boolean> {
603 try {
604 const controller = new AbortController()
605 const timeoutId = setTimeout(() => controller.abort(), 2000) // 2秒快速超时,避免影响页面加载
606
607 const response = await fetch(WALLPAPER_SERVICE_CONFIG.fullUrl, {
608 method: 'GET',
609 headers: {
610 'Accept': 'application/json',
611 },
612 signal: controller.signal
613 })
614
615 clearTimeout(timeoutId)
616
617 if (!response.ok) {
618 return false
619 }
620
621 const data = await response.json()
622 if (!data.images || !Array.isArray(data.images) || data.images.length === 0) {
623 return false
624 }
625
626 return true
627 } catch (error) {
628 return false
629 }
630}
631
632// 服务状态监控(仅在使用备用图片时运行)
633async function monitorServiceStatus() {
634 if (!isUsingFallback) {
635 // 如果不是使用备用图片,停止监控
636 stopServiceMonitoring()
637 return
638 }
639
640 console.log('🔍 检查图集服务状态...')
641 const isServiceAvailable = await checkServiceAvailability()
642
643 if (isServiceAvailable) {
644 console.log('✅ 图集服务已恢复!切换回动态图库')
645
646 // 停止服务监控
647 stopServiceMonitoring()
648
649 // 重新获取动态图库
650 await fetchImageLibrary()
651
652 // 立即切换到动态图片
653 if (!isUsingFallback && currentImages.length > 0) {
654 await displayRandomImage()
655 }
656 }
657}
658
659// 停止服务监控
660function stopServiceMonitoring() {
661 if (serviceCheckIntervalId) {
662 clearInterval(serviceCheckIntervalId)
663 serviceCheckIntervalId = null
664 console.log('📴 停止图集服务监控')
665 }
666}
667
668// 启动服务监控(仅在切换到备用图片时启动)
669function startServiceMonitoring() {
670 if (!serviceCheckIntervalId) {
671 serviceCheckIntervalId = window.setInterval(monitorServiceStatus, SERVICE_CHECK_INTERVAL)
672 console.log('👁️ 启动图集服务监控(15秒检测一次)')
673 }
674}
675
676// 获取备用图片列表
677function getFallbackImages(): string[] {
678 // 从 Wallaper.ts 导入的备用图片
679 const fallbackImages = [
680 "https://img.xxdevops.cn/blog/wallpaper/bg01.avif",
681 "https://img.xxdevops.cn/blog/wallpaper/bg02.avif",
682 "https://img.xxdevops.cn/blog/wallpaper/bg03.avif",
683 "https://img.xxdevops.cn/blog/wallpaper/bg04.avif",
684 "https://img.xxdevops.cn/blog/wallpaper/bg05.avif",
685 "https://img.xxdevops.cn/blog/wallpaper/bg06.avif",
686 "https://img.xxdevops.cn/blog/wallpaper/bg07.avif",
687 "https://img.xxdevops.cn/blog/wallpaper/bg08.avif",
688 "https://img.xxdevops.cn/blog/wallpaper/bg09.avif",
689 "https://img.xxdevops.cn/blog/wallpaper/bg10.avif",
690 "https://img.xxdevops.cn/blog/wallpaper/bg11.avif",
691 "https://img.xxdevops.cn/blog/wallpaper/bg12.avif",
692 "https://img.xxdevops.cn/blog/wallpaper/bg13.avif",
693 "https://img.xxdevops.cn/blog/wallpaper/bg14.avif",
694 "https://img.xxdevops.cn/blog/wallpaper/bg15.avif",
695 "https://img.xxdevops.cn/blog/wallpaper/bg16.avif",
696 "https://img.xxdevops.cn/blog/wallpaper/bg17.avif",
697 "https://img.xxdevops.cn/blog/wallpaper/bg18.avif",
698 "https://img.xxdevops.cn/blog/wallpaper/bg19.avif",
699 "https://img.xxdevops.cn/blog/wallpaper/bg20.avif"
700 ]
701 return fallbackImages
702}
703
704// 页面刷新时从API获取新壁纸
705async function fetchFreshWallpaperOnRefresh(): Promise<string | null> {
706 try {
707 console.log('🔄 页面刷新,从API获取新壁纸...')
708 const images = await fetchDynamicWallpapers()
709
710 // 检查是否获取到有效的动态图片(非备用图片)
711 const fallbackImages = getFallbackImages()
712 const isDynamicImages = images.some(img => !fallbackImages.includes(img))
713
714 if (isDynamicImages && images.length > 0) {
715 // 成功获取到动态图库
716 currentImages = images
717 lastSuccessfulFetch = Date.now()
718 isUsingFallback = false
719
720 // 保存图库到缓存
721 saveImagesToCache(images)
722
723 // 随机选择一张新壁纸,排除缓存的上一张
724 const lastImage = getLastImage()
725 let availableImages = images
726
727 // 如果有上次的图片且图库有多张图,排除上次的图片
728 if (lastImage && images.length > 1) {
729 availableImages = images.filter(img => img !== lastImage)
730 }
731
732 // 如果过滤后没有图片了,使用全部图片
733 if (availableImages.length === 0) {
734 availableImages = images
735 }
736
737 const randomImg = availableImages[Math.floor(Math.random() * availableImages.length)]
738 console.log(`🎨 刷新获取新壁纸成功: ${randomImg}`)
739
740 return randomImg
741 } else {
742 throw new Error('未获取到有效的动态图片')
743 }
744 } catch (error) {
745 console.warn('刷新获取新壁纸失败,使用备用图片:', error)
746
747 // 使用备用图片
748 const fallbackImages = getFallbackImages()
749 currentImages = fallbackImages
750 isUsingFallback = true
751
752 // 保存备用图库到缓存
753 saveImagesToCache(fallbackImages)
754
755 // 启动服务监控
756 startServiceMonitoring()
757
758 // 从备用图片中随机选择
759 const lastImage = getLastImage()
760 let availableImages = fallbackImages
761
762 if (lastImage && fallbackImages.length > 1) {
763 availableImages = fallbackImages.filter(img => img !== lastImage)
764 }
765
766 if (availableImages.length === 0) {
767 availableImages = fallbackImages
768 }
769
770 const randomImg = availableImages[Math.floor(Math.random() * availableImages.length)]
771 console.log(`🔄 使用备用壁纸: ${randomImg}`)
772
773 return randomImg
774 }
775}
776
777// 从图集服务器获取图库列表
778async function fetchImageLibrary() {
779 try {
780 const images = await fetchDynamicWallpapers()
781
782 // 检查是否获取到有效的动态图片(非备用图片)
783 const fallbackImages = getFallbackImages()
784 const isDynamicImages = images.some(img => !fallbackImages.includes(img))
785
786 if (isDynamicImages && images.length > 0) {
787 // 成功获取到动态图库
788 currentImages = images
789 lastSuccessfulFetch = Date.now()
790
791 // 保存图库到缓存
792 saveImagesToCache(images)
793
794 if (isUsingFallback) {
795 console.log('✅ 图集服务已恢复,切换回动态图库')
796 isUsingFallback = false
797 // 停止服务监控
798 stopServiceMonitoring()
799 }
800
801 console.log(`📚 动态图库已更新: ${images.length} 张图片`)
802 } else {
803 // 没有获取到有效的动态图片,使用备用图片
804 if (!isUsingFallback) {
805 console.log('⚠️ 图集服务不可用,切换到备用图片')
806 currentImages = fallbackImages
807 isUsingFallback = true
808 // 启动服务监控
809 startServiceMonitoring()
810 }
811 }
812 } catch (error) {
813 console.warn('获取图库失败,使用备用图片:', error)
814
815 if (!isUsingFallback) {
816 currentImages = getFallbackImages()
817 isUsingFallback = true
818 // 启动服务监控
819 startServiceMonitoring()
820 }
821 }
822}
823
824onMounted(async () => {
825 // 检查是否为首页
826 isHomePage = checkIsHomePage()
827 console.log('🖼️ 动态壁纸管理器启动 - 当前页面:', route.path, '是否首页:', isHomePage)
828
829 if (!isHomePage) {
830 console.log('⏹️ 非首页,动态壁纸系统不启动')
831 return
832 }
833
834 // 在生产构建时,增加延迟确保DOM完全ready
835 const isProduction = import.meta.env.PROD
836 const mountDelay = isProduction ? 100 : 0
837
838 // 使用setTimeout确保在hydration完成后再执行
839 setTimeout(async () => {
840 // 确保banner容器正确设置
841 ensureBannerContentStability()
842
843 // 显示主题背景色作为加载状态,避免空白
844 const bannerEl = document.querySelector('.tk-banner') as HTMLElement
845 if (bannerEl) {
846 bannerEl.style.background = 'var(--vp-c-bg)'
847 bannerEl.style.backgroundColor = 'var(--vp-c-bg)'
848 console.log('🎨 设置加载背景色,避免空白显示')
849 }
850
851 // 优先从API获取新的壁纸
852 try {
853 const freshWallpaper = await fetchFreshWallpaperOnRefresh()
854 if (freshWallpaper) {
855 currentDisplayImage = freshWallpaper
856 // 使用新获取的壁纸立即显示
857 const success = initBannerBackground(freshWallpaper)
858 if (success) {
859 console.log('🎯 页面刷新成功显示新壁纸:', freshWallpaper)
860 } else {
861 throw new Error('壁纸显示失败')
862 }
863 } else {
864 throw new Error('未获取到新壁纸')
865 }
866 } catch (error) {
867 console.warn('从API获取新壁纸失败,使用降级方案:', error)
868
869 // 降级方案:使用缓存或备用壁纸
870 const lastImage = getLastImage()
871 const cachedImages = loadImagesFromCache()
872
873 if (lastImage) {
874 console.log('📦 降级:使用缓存的上次壁纸:', lastImage)
875 currentDisplayImage = lastImage
876 initBannerBackground(lastImage)
877
878 if (cachedImages.length > 0) {
879 currentImages = cachedImages
880 } else {
881 currentImages = getFallbackImages()
882 }
883 } else if (cachedImages.length > 0) {
884 console.log('📦 降级:使用缓存图库随机壁纸')
885 currentImages = cachedImages
886 const randomImg = cachedImages[Math.floor(Math.random() * cachedImages.length)]
887 currentDisplayImage = randomImg
888 initBannerBackground(randomImg)
889 } else {
890 console.log('📦 降级:使用备用壁纸')
891 currentImages = getFallbackImages()
892 const randomImg = currentImages[Math.floor(Math.random() * currentImages.length)]
893 currentDisplayImage = randomImg
894 initBannerBackground(randomImg)
895 }
896 }
897 }, mountDelay)
898
899 // 页面可见性变化监听 - 优化性能
900 const handleVisibilityChange = () => {
901 const bannerEl = document.querySelector('.tk-banner') as HTMLElement
902
903 if (bannerEl) {
904 if (document.hidden) {
905 // 页面隐藏时暂停伪元素动画
906 bannerEl.classList.add('paused')
907 } else {
908 // 页面可见时恢复伪元素动画
909 bannerEl.classList.remove('paused')
910 }
911 }
912
913 // 同时处理其他可能的背景元素
914 const backgroundElements = document.querySelectorAll('.wallpaper-breathing')
915 backgroundElements.forEach(element => {
916 if (document.hidden) {
917 element.classList.add('paused')
918 } else {
919 element.classList.remove('paused')
920 }
921 })
922 }
923
924 document.addEventListener('visibilitychange', handleVisibilityChange)
925
926 // 等待一个切换间隔后开始异步检测和切换
927 setTimeout(async () => {
928 console.log('🔍 开始检测图集服务状态并更新图库...')
929
930 try {
931 const controller = new AbortController()
932 const timeoutId = setTimeout(() => controller.abort(), 2000) // 2秒检测
933
934 const response = await fetch(WALLPAPER_SERVICE_CONFIG.fullUrl, {
935 method: 'GET',
936 headers: { 'Accept': 'application/json' },
937 signal: controller.signal
938 })
939
940 clearTimeout(timeoutId)
941
942 if (response.ok) {
943 // 服务可用:获取最新图库
944 console.log('✅ 图集服务可用,更新动态图库')
945 await fetchImageLibrary()
946
947 // 设置定时器:每60秒更新图库
948 fetchLibraryIntervalId = window.setInterval(fetchImageLibrary, FETCH_LIBRARY_INTERVAL)
949 } else {
950 throw new Error('服务响应异常')
951 }
952 } catch (error) {
953 // 服务不可用:确保使用备用图片并启动监控
954 console.log('❌ 图集服务不可用,确保使用备用壁纸')
955 if (!isUsingFallback) {
956 currentImages = getFallbackImages()
957 isUsingFallback = true
958 saveImagesToCache(currentImages) // 保存备用图库到缓存
959 }
960
961 // 启动服务监控,等待服务恢复
962 startServiceMonitoring()
963 }
964 }, isProduction ? 1500 : 1000) // 生产环境增加延迟,确保缓存壁纸已显示
965
966 // 设置定时器:每10秒切换图片(确保统一的切换间隔)
967 switchImageIntervalId = window.setInterval(displayRandomImage, SWITCH_IMAGE_INTERVAL)
968 console.log(`⏰ 壁纸切换定时器已启动,间隔: ${SWITCH_IMAGE_INTERVAL / 1000}秒`)
969
970 // 初始化基本设置(CSS变量方案下大大简化)
971 setTimeout(() => {
972 ensureBannerContentStability()
973 console.log('🌟 CSS变量背景系统初始化完成')
974 }, 500)
975
976 // 监听路由变化
977 watch(() => route.path, (newPath) => {
978 const newIsHomePage = checkIsHomePage()
979 console.log('🧭 路由变化:', newPath, '是否首页:', newIsHomePage)
980
981 if (newIsHomePage !== isHomePage) {
982 isHomePage = newIsHomePage
983
984 if (isHomePage) {
985 console.log('🏠 切换到首页,启动动态壁纸系统')
986 // 如果切换回首页,重新启动系统(但此时组件应该重新挂载)
987 location.reload() // 简单重载确保干净状态
988 } else {
989 console.log('🚪 离开首页,停止动态壁纸系统')
990 stopWallpaperSystem()
991 }
992 }
993 })
994})
995
996onUnmounted(() => {
997 // 清理图库获取定时器
998 if (fetchLibraryIntervalId) {
999 clearInterval(fetchLibraryIntervalId)
1000 fetchLibraryIntervalId = null
1001 }
1002
1003 // 清理图片切换定时器
1004 if (switchImageIntervalId) {
1005 clearInterval(switchImageIntervalId)
1006 switchImageIntervalId = null
1007 }
1008
1009 // 清理服务检测定时器
1010 stopServiceMonitoring()
1011
1012 // 清理页面可见性监听器
1013 document.removeEventListener('visibilitychange', () => {})
1014
1015 console.log('🖼️ 动态壁纸管理器已停止')
1016})
1017</script>
1018
1019<style>
1020/* 壁纸呼吸动画效果 - 增强可见性版本 */
1021@keyframes wallpaper-breathing {
1022 0%, 100% {
1023 transform: scale(1) translateZ(0);
1024 filter: brightness(1) contrast(1) saturate(1);
1025 }
1026 50% {
1027 transform: scale(1.025) translateZ(0); /* 增加缩放幅度让效果更明显 */
1028 filter: brightness(1.12) contrast(1.05) saturate(1.05); /* 增强滤镜效果 */
1029 }
1030}
1031
1032/* 呼吸动画类 - 限制为首页专用,避免影响其他页面 */
1033.VPHome .wallpaper-breathing,
1034.VPHome .tk-banner .wallpaper-breathing,
1035.VPHome .tk-banner .tk-banner__bg.wallpaper-breathing,
1036.VPHome .tk-banner .tk-banner-bg.wallpaper-breathing,
1037.VPHome [style*="background-image"].wallpaper-breathing {
1038 animation: wallpaper-breathing 6s ease-in-out infinite !important;
1039 transform-origin: center center !important;
1040 will-change: transform, filter;
1041}
1042
1043/* 壁纸淡入淡出过渡样式 - 确保背景图片层次正确 */
1044.tk-banner .tk-banner__bg,
1045.tk-banner .tk-banner-bg,
1046.tk-banner [class*="banner"][class*="bg"],
1047.banner-bg,
1048.tk-banner-bg,
1049.background-image {
1050 background-blend-mode: normal;
1051 /* 确保多层背景正确显示 */
1052 /* 添加硬件加速以获得更流畅的过渡 */
1053 backface-visibility: hidden;
1054 -webkit-backface-visibility: hidden;
1055 transform: translateZ(0);
1056 /* 预设过渡效果,使用最佳的缓动函数 */
1057 transition: opacity 1.5s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1058 /* 确保图片切换时不会有空白背景 */
1059 background-color: transparent;
1060 /* 优化渲染性能 */
1061 will-change: opacity, transform;
1062}
1063
1064/* 确保背景图片容器有足够的层级 */
1065[style*="background-image"] {
1066 position: relative;
1067 overflow: hidden;
1068 /* 添加硬件加速 */
1069 transform: translateZ(0);
1070 backface-visibility: hidden;
1071 -webkit-backface-visibility: hidden;
1072 /* 优化过渡效果 */
1073 transition: opacity 1.5s cubic-bezier(0.4, 0, 0.2, 1);
1074 /* 确保无背景色干扰 */
1075 background-color: transparent;
1076 /* 优化渲染性能 */
1077 will-change: opacity;
1078}
1079
1080/* 双图层无缝切换背景系统 - 限制为首页专用 */
1081.VPHome .tk-banner {
1082 position: relative;
1083 z-index: 0;
1084 overflow: hidden; /* 隐藏背景溢出,避免产生滚动条 */
1085 /* 创建独立的层叠上下文但不影响子元素 */
1086 isolation: isolate;
1087
1088 /* 使用主题背景色避免蓝色闪烁,与blogger-fix.scss保持一致 */
1089 background: var(--vp-c-bg);
1090 background-color: var(--vp-c-bg);
1091
1092 /* 图层A的CSS变量 */
1093 --layer-a-bg-image: none;
1094 --layer-a-opacity: 0;
1095
1096 /* 图层B的CSS变量 */
1097 --layer-b-bg-image: none;
1098 --layer-b-opacity: 0;
1099
1100 /* 通用控制变量 */
1101 --bg-transition-duration: 2s;
1102}
1103
1104/* 生产环境防闪现优化 - 立即隐藏可能的默认背景元素 */
1105.VPHome .tk-banner .tk-banner__bg,
1106.VPHome .tk-banner .tk-banner-bg,
1107.VPHome .tk-banner [class*="banner"][class*="bg"],
1108.VPHome .banner-bg {
1109 opacity: 0 !important;
1110 visibility: hidden !important;
1111 z-index: -1000 !important;
1112 transition: none !important;
1113}
1114
1115/* 图层A - 使用::before伪元素 - 限制为首页专用 */
1116.VPHome .tk-banner::before {
1117 content: '';
1118 position: absolute;
1119 top: 0;
1120 left: 0;
1121 right: 0;
1122 bottom: 0;
1123 background-image: var(--layer-a-bg-image);
1124 background-size: cover;
1125 background-position: center center;
1126 background-repeat: no-repeat;
1127 background-color: transparent;
1128 opacity: var(--layer-a-opacity);
1129 transition: opacity var(--bg-transition-duration) cubic-bezier(0.4, 0, 0.2, 1);
1130 z-index: -2; /* 在图层B下方 */
1131 pointer-events: none;
1132 /* 硬件加速 */
1133 will-change: opacity, transform, filter;
1134 backface-visibility: hidden;
1135 transform: translateZ(0);
1136 /* 呼吸动画 - 图层A,同步时机 */
1137 animation: wallpaper-breathing 8s ease-in-out infinite;
1138 animation-delay: 0s;
1139 transform-origin: center center;
1140}
1141
1142/* 图层B - 使用::after伪元素 - 限制为首页专用 */
1143.VPHome .tk-banner::after {
1144 content: '';
1145 position: absolute;
1146 top: 0;
1147 left: 0;
1148 right: 0;
1149 bottom: 0;
1150 background-image: var(--layer-b-bg-image);
1151 background-size: cover;
1152 background-position: center center;
1153 background-repeat: no-repeat;
1154 background-color: transparent;
1155 opacity: var(--layer-b-opacity);
1156 transition: opacity var(--bg-transition-duration) cubic-bezier(0.4, 0, 0.2, 1);
1157 z-index: -1; /* 在图层A上方 */
1158 pointer-events: none;
1159 /* 硬件加速 */
1160 will-change: opacity, transform, filter;
1161 backface-visibility: hidden;
1162 transform: translateZ(0);
1163 /* 呼吸动画 - 图层B,与图层A完全同步 */
1164 animation: wallpaper-breathing 8s ease-in-out infinite;
1165 animation-delay: 0s;
1166 transform-origin: center center;
1167}
1168
1169/* 图层显示控制 - 限制为首页专用 */
1170/* 默认状态下两个图层都显示但透明 */
1171.VPHome .tk-banner::before,
1172.VPHome .tk-banner::after {
1173 display: block;
1174 opacity: 0;
1175}
1176
1177/* 有背景时显示对应图层 */
1178.VPHome .tk-banner.has-layer-a::before {
1179 display: block;
1180 opacity: var(--layer-a-opacity);
1181}
1182
1183.VPHome .tk-banner.has-layer-b::after {
1184 display: block;
1185 opacity: var(--layer-b-opacity);
1186}
1187
1188/* 图层加载完成后移除预设背景 */
1189.VPHome .tk-banner.has-layer-a.background-loaded,
1190.VPHome .tk-banner.has-layer-b.background-loaded {
1191 background: none !important;
1192 background-color: transparent !important;
1193}
1194
1195/* 双图层同时显示时用于无缝切换 */
1196.VPHome .tk-banner.dual-layer::before,
1197.VPHome .tk-banner.dual-layer::after {
1198 display: block;
1199}
1200
1201/* 页面隐藏时暂停两个图层的动画 */
1202.VPHome .tk-banner.paused::before,
1203.VPHome .tk-banner.paused::after {
1204 animation-play-state: paused;
1205}
1206
1207/* 动画时机优化 - 确保图层可见时播放动画 */
1208.VPHome .tk-banner.has-layer-a::before {
1209 animation-play-state: running;
1210}
1211
1212.VPHome .tk-banner.has-layer-b::after {
1213 animation-play-state: running;
1214}
1215
1216/* 默认情况下也允许动画运行,避免动画被意外暂停 */
1217.VPHome .tk-banner::before,
1218.VPHome .tk-banner::after {
1219 animation-play-state: running;
1220}
1221
1222/* 双图层模式下确保两个动画同步 */
1223.VPHome .tk-banner.dual-layer::before,
1224.VPHome .tk-banner.dual-layer::after {
1225 animation-play-state: running;
1226 /* 确保两个图层的动画开始时间一致 */
1227 animation-delay: 0s;
1228}
1229
1230/* 移动端优化呼吸动画 - 应用到双图层 - 限制为首页专用 */
1231@media (max-width: 768px) {
1232 .VPHome .tk-banner::before,
1233 .VPHome .tk-banner::after {
1234 animation-duration: 12s;
1235 /* 移动端减少性能消耗 */
1236 will-change: opacity;
1237 }
1238
1239 /* 移动端呼吸动画重定义 */
1240 @keyframes wallpaper-breathing {
1241 0%, 100% {
1242 transform: scale(1) translateZ(0);
1243 filter: brightness(1);
1244 }
1245 50% {
1246 transform: scale(1.008) translateZ(0); /* 移动端进一步减少缩放 */
1247 filter: brightness(1.05);
1248 }
1249 }
1250}
1251
1252/* 保留旧背景元素的动画支持,但优先使用伪元素 - 限制为首页专用 */
1253.VPHome .tk-banner .tk-banner__bg,
1254.VPHome .tk-banner .tk-banner-bg,
1255.VPHome .tk-banner [class*="banner"][class*="bg"] {
1256 animation: wallpaper-breathing 8s ease-in-out infinite;
1257 transform-origin: center center;
1258 will-change: transform, filter;
1259}
1260
1261.VPHome .tk-banner .wallpaper-breathing {
1262 animation-duration: 10s;
1263}
1264
1265/* 移动端旧背景元素呼吸动画优化 */
1266@media (max-width: 768px) {
1267 .VPHome .wallpaper-breathing {
1268 animation-duration: 12s; /* 移动端使用更缓慢的呼吸频率 */
1269 }
1270}
1271
1272/* 暂停动画的类(用于页面切换时) - 限制为首页专用 */
1273.VPHome .wallpaper-breathing.paused {
1274 animation-play-state: paused;
1275}
1276
1277/* 临时图层样式(现在主要用于向后兼容) */
1278.wallpaper-temp-layer {
1279 backface-visibility: hidden;
1280 transform: translateZ(-1px);
1281 will-change: opacity;
1282 z-index: -50;
1283 position: absolute;
1284 pointer-events: none;
1285}
1286
1287/* 确保过渡期间不会有闪烁 */
1288.tk-banner .tk-banner__bg,
1289.tk-banner .tk-banner-bg,
1290.tk-banner [class*="banner"][class*="bg"] {
1291 /* 防止在切换时显示空白背景 */
1292 min-height: 100%;
1293 background-attachment: local;
1294}
1295
1296/* 简化的内容层保护 - 基于CSS变量的稳定方案 */
1297.tk-banner-content,
1298.tk-banner .tk-banner-content,
1299.banner-content {
1300 position: relative;
1301 z-index: 10;
1302 /* 确保内容层稳定显示 */
1303 isolation: isolate;
1304 /* 确保内容区域不被隐藏的背景overflow影响 */
1305 overflow: visible;
1306}
1307
1308/* 所有banner内容子元素的基本保护 */
1309.tk-banner-content *,
1310.tk-banner .tk-banner-content * {
1311 position: relative;
1312 z-index: 11;
1313}
1314
1315/* 确保所有banner内部内容都不受背景overflow:hidden影响 */
1316.tk-banner > *:not(::before):not(::after) {
1317 position: relative;
1318 z-index: 5;
1319 /* 确保内容层有自己的显示空间 */
1320 overflow: visible;
1321}
1322
1323/* 特别保护常见的banner内容元素 */
1324.tk-banner .tk-banner-title,
1325.tk-banner .tk-banner-subtitle,
1326.tk-banner .tk-banner-description,
1327.tk-banner .tk-banner-buttons,
1328.tk-banner .banner-content,
1329.tk-banner .banner-text {
1330 position: relative;
1331 z-index: 15;
1332 overflow: visible;
1333}
1334
1335/* 确保旧的背景元素正确定位 */
1336.tk-banner .tk-banner__bg,
1337.tk-banner .tk-banner-bg {
1338 position: absolute;
1339 top: 0;
1340 left: 0;
1341 right: 0;
1342 bottom: 0;
1343 z-index: -1;
1344 pointer-events: none;
1345}
1346
1347/* 确保banner的层级结构正确 - 限制为首页专用 */
1348.VPHome .tk-banner {
1349 /* 确保banner容器有合适的层级上下文 */
1350 z-index: 1;
1351}
1352
1353.VPHome .tk-banner > *:not(.tk-banner__bg):not(.tk-banner-bg):not([class*="banner"][class*="bg"]) {
1354 /* 确保所有非背景的banner子元素都在背景之上 */
1355 position: relative;
1356 z-index: 2;
1357}
1358</style>(3)修改docs\index.md文件:
添加如下2行内容:
1import DynamicWallpaperManager from "./.vitepress/theme/components/DynamicWallpaperManager.vue"; //导入壁纸api组件
2
3
4<!-- 首页专用动态壁纸组件 -->
5<DynamicWallpaperManager />

(4)创建docs\.vitepress\theme\style\blogger-fix.scss文件:
1/* 修复博主信息卡片背景图层级问题 */
2
3/* 覆盖壁纸加载时的蓝色背景闪烁 */
4.tk-banner {
5 /* 只在非首页移除背景,首页保留动态壁纸功能 */
6 background: transparent !important;
7 background-color: transparent !important;
8}
9
10/* 首页动态壁纸专用样式 - 优先级更高 */
11.VPHome .tk-banner {
12 background: var(--vp-c-bg) !important;
13 background-color: var(--vp-c-bg) !important;
14}
15
16/* 壁纸加载完成后移除预设背景 */
17.VPHome .tk-banner.background-loaded {
18 background: none !important;
19 background-color: transparent !important;
20}
21
22/* 确保背景图在最底层 */
23.tk-my__avatar__circle--bg {
24 position: absolute !important;
25 z-index: 1 !important;
26 top: 0;
27 left: 0;
28 width: 100%;
29 height: 100%;
30 background-size: cover !important;
31 background-position: center !important;
32 background-repeat: no-repeat !important;
33}
34
35/* 确保头像在背景图之上 */
36.tk-my__avatar {
37 position: relative !important;
38 z-index: 2 !important;
39}
40
41/* 确保博主信息文字在背景图之上 */
42.tk-my__blogger {
43 position: relative !important;
44 z-index: 2 !important;
45 color: #fff !important;
46}
47
48/* 确保社交图标在背景图之上 */
49.tk-my__icons {
50 position: relative !important;
51 z-index: 2 !important;
52}
53
54/* 确保整个博主信息卡片有正确的层级结构 */
55.tk-my.is-circle-bg {
56 position: relative !important;
57 overflow: hidden;
58}
59
60/* 优化文字可读性 */
61.tk-my__blogger .name {
62 color: #fff !important;
63 text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
64 font-weight: bold;
65}
66
67.tk-my__blogger .slogan {
68 color: rgba(255, 255, 255, 0.9) !important;
69 text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
70}再编辑下docs\.vitepress\theme\style\index.scss文件:(使用下刚才创建的scss文件)
1@use "./blogger-fix.scss" as *; // 修复博主信息卡片背景图层级问题(5)验证。
运行项目,验证效果。pnpm docs:dev
效果见上文。(完美😜)
以上我们的Go壁纸api 就接入Teek了,此时你只需要有空把好看的妹纸、风景照片存到 壁纸api后台,Teek首页就会立马看到你的好看照片了哦。😜(快去试试吧)

关于我的 go壁纸api 项目是如何部署的,请看开源链接《img-server》

结束。
日历 · 精选 · 友链 · 更多
如果内容对你有帮助,欢迎请我喝杯咖啡 ☕



One的公众号
爱折腾博客的小白