mirror of
https://gitee.com/wot-design-uni/wot-design-uni.git
synced 2025-12-06 09:08:51 +08:00
docs: ✏️ 添加 banner 显示逻辑 (#1347)
* docs: ✏️ 添加 banner 显示逻辑 * docs: ✏️ 使用 onMounted 钩子避免服务端渲染时的闪烁问题
This commit is contained in:
parent
ec57b72224
commit
5c4d8dd8dc
275
docs/.vitepress/theme/components/Banner.vue
Normal file
275
docs/.vitepress/theme/components/Banner.vue
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, computed, onMounted } from 'vue'
|
||||||
|
import { useBanner } from '../composables/banner'
|
||||||
|
|
||||||
|
const open = ref(false) // 默认不显示,避免闪烁
|
||||||
|
const BANNER_STORAGE_KEY = 'wot-banner-dismissed-time'
|
||||||
|
const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000 // 24小时的毫秒数
|
||||||
|
|
||||||
|
// 使用 banner composable 获取远程数据
|
||||||
|
const { data: bannerData } = useBanner()
|
||||||
|
|
||||||
|
// 计算当前要显示的 banner 信息(取第一个)
|
||||||
|
const currentBanner = computed(() => {
|
||||||
|
return bannerData.value && bannerData.value.length > 0 ? bannerData.value[0] : null
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 默认添加 banner-dismissed class,避免布局闪烁
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
document.documentElement.classList.add('banner-dismissed')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否应该显示横幅
|
||||||
|
*/
|
||||||
|
function checkShouldShowBanner() {
|
||||||
|
if (typeof window === 'undefined') return true
|
||||||
|
|
||||||
|
const dismissedTime = localStorage.getItem(BANNER_STORAGE_KEY)
|
||||||
|
if (!dismissedTime) {
|
||||||
|
// 首次访问,移除 banner-dismissed class 以显示横幅
|
||||||
|
document.documentElement.classList.remove('banner-dismissed')
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const dismissedTimestamp = parseInt(dismissedTime, 10)
|
||||||
|
const currentTime = Date.now()
|
||||||
|
|
||||||
|
// 如果超过24小时,清除记录并显示横幅
|
||||||
|
if (currentTime - dismissedTimestamp > TWENTY_FOUR_HOURS) {
|
||||||
|
localStorage.removeItem(BANNER_STORAGE_KEY)
|
||||||
|
document.documentElement.classList.remove('banner-dismissed')
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未超过24小时,保持 banner-dismissed class(已经默认添加了)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function dismiss() {
|
||||||
|
open.value = false
|
||||||
|
document.documentElement.classList.add('banner-dismissed')
|
||||||
|
|
||||||
|
// 存储当前时间戳到 localStorage
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
localStorage.setItem(BANNER_STORAGE_KEY, Date.now().toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听 banner 数据变化,只有当有数据时才进行展示逻辑校验
|
||||||
|
watch(currentBanner, (newBanner) => {
|
||||||
|
if (newBanner) {
|
||||||
|
// 有 banner 数据时,检查是否应该显示
|
||||||
|
const shouldShow = checkShouldShowBanner()
|
||||||
|
open.value = shouldShow
|
||||||
|
} else {
|
||||||
|
// 没有 banner 数据时,不显示横幅
|
||||||
|
open.value = false
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="banner" v-if="open && currentBanner">
|
||||||
|
|
||||||
|
<div class="vt-banner-text">
|
||||||
|
<p class="vt-banner-title">{{ currentBanner.title }}</p>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
class="vt-primary-action"
|
||||||
|
:href="currentBanner.link"
|
||||||
|
>
|
||||||
|
{{ currentBanner.action }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<button aria-label="close" @click="dismiss">
|
||||||
|
<svg
|
||||||
|
class="close"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
aria-hidden="true"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M18.9,10.9h-6v-6c0-0.6-0.4-1-1-1s-1,0.4-1,1v6h-6c-0.6,0-1,0.4-1,1s0.4,1,1,1h6v6c0,0.6,0.4,1,1,1s1-0.4,1-1v-6h6c0.6,0,1-0.4,1-1S19.5,10.9,18.9,10.9z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div class="glow glow--purple"></div>
|
||||||
|
<div class="glow glow--blue"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
html:not(.banner-dismissed) {
|
||||||
|
--vp-layout-top-height: 64px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.banner {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 100;
|
||||||
|
box-sizing: border-box;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: var(--vp-layout-top-height);
|
||||||
|
line-height: var(--vp-layout-top-height);
|
||||||
|
text-align: center;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: white;
|
||||||
|
background: #131A24;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow.glow--purple {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -15%;
|
||||||
|
left: -75%;
|
||||||
|
width: 80%;
|
||||||
|
aspect-ratio: 1.5;
|
||||||
|
pointer-events: none;
|
||||||
|
border-radius: 100%;
|
||||||
|
background: linear-gradient(270deg, #7a23a1, #715ebde6 60% 80%, #bd34fe00);
|
||||||
|
filter: blur(15vw);
|
||||||
|
transform: none;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow.glow--blue {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -15%;
|
||||||
|
right: -40%;
|
||||||
|
width: 80%;
|
||||||
|
aspect-ratio: 1.5;
|
||||||
|
pointer-events: none;
|
||||||
|
border-radius: 100%;
|
||||||
|
background: linear-gradient(180deg, #61d9ff, #0000);
|
||||||
|
filter: blur(15vw);
|
||||||
|
transform: none;
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.glow.glow--blue {
|
||||||
|
top: -15%;
|
||||||
|
right: -40%;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow.glow--purple {
|
||||||
|
bottom: -15%;
|
||||||
|
left: -40%;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1025px) {
|
||||||
|
.glow.glow--blue {
|
||||||
|
top: -15%;
|
||||||
|
right: -40%;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow.glow--purple {
|
||||||
|
bottom: -15%;
|
||||||
|
left: -40%;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-dismissed .banner {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
padding: 5px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
fill: #fff;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vt-banner-text {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 20px;
|
||||||
|
margin-left: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vt-banner-title {
|
||||||
|
display: inline-block;
|
||||||
|
background: linear-gradient(90deg, #bd34fe 0%, #41d1ff 100%);
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
color: transparent;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 18px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vt-primary-action {
|
||||||
|
background:
|
||||||
|
radial-gradient(140.35% 140.35% at 175% 94.74%, #2bfdd2, #bd34fe00),
|
||||||
|
radial-gradient(89.94% 89.94% at 18.42% 15.79%, #4d80f0, #41d1ff00);
|
||||||
|
color: #fff;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 18px;
|
||||||
|
text-decoration: none;
|
||||||
|
margin: 0 0.75rem;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1280px) {
|
||||||
|
.banner .vt-banner-text,
|
||||||
|
.banner .vt-primary-action {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vt-tagline {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 780px) {
|
||||||
|
.vt-tagline {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vt-coupon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vt-primary-action {
|
||||||
|
margin: 0 10px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vt-time-now {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 560px) {
|
||||||
|
.vt-place {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -230,7 +230,7 @@ watch(
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
right: 32px;
|
right: 32px;
|
||||||
top: calc(var(--vp-nav-height) + 32px);
|
top: calc(var(--vp-nav-height) + var(--vp-layout-top-height) + 32px);
|
||||||
width: 330px;
|
width: 330px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
background: var(--vp-c-bg-alt);
|
background: var(--vp-c-bg-alt);
|
||||||
@ -360,7 +360,7 @@ watch(
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
right: 16px;
|
right: 16px;
|
||||||
top: calc(var(--vp-nav-height) + 32px);
|
top: calc(var(--vp-nav-height) + var(--vp-layout-top-height) + 32px);
|
||||||
width: 48px;
|
width: 48px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
background: var(--vp-c-bg-alt);
|
background: var(--vp-c-bg-alt);
|
||||||
|
|||||||
46
docs/.vitepress/theme/composables/banner.ts
Normal file
46
docs/.vitepress/theme/composables/banner.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
export type BannerData = {
|
||||||
|
action: string
|
||||||
|
title: string
|
||||||
|
link: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = ref<BannerData[]>([])
|
||||||
|
|
||||||
|
export function useBanner() {
|
||||||
|
onMounted(async () => {
|
||||||
|
// 定义数据源URL列表,按优先级排序
|
||||||
|
const urls = [
|
||||||
|
'https://sponsor.wot-ui.cn/banner.json',
|
||||||
|
'https://wot-sponsors.pages.dev/banner.json'
|
||||||
|
]
|
||||||
|
|
||||||
|
// 尝试从多个数据源获取数据
|
||||||
|
const fetchData = async () => {
|
||||||
|
for (const url of urls) {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(url + '?t=' + Date.now(), {
|
||||||
|
timeout: 5000 // 设置5秒超时
|
||||||
|
})
|
||||||
|
return response.data && response.data.data ? response.data.data : [] // 成功获取数据后直接返回
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to fetch from ${url}`)
|
||||||
|
// 继续尝试下一个URL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [] // 所有数据源都失败时返回空数组
|
||||||
|
}
|
||||||
|
|
||||||
|
data.value = await fetchData()
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: weisheng
|
* @Author: weisheng
|
||||||
* @Date: 2024-10-12 22:09:33
|
* @Date: 2024-10-12 22:09:33
|
||||||
* @LastEditTime: 2025-09-21 19:40:24
|
* @LastEditTime: 2025-10-30 11:09:04
|
||||||
* @LastEditors: weisheng
|
* @LastEditors: weisheng
|
||||||
* @Description:
|
* @Description:
|
||||||
* @FilePath: /wot-design-uni/docs/.vitepress/theme/index.ts
|
* @FilePath: /wot-design-uni/docs/.vitepress/theme/index.ts
|
||||||
@ -21,6 +21,7 @@ import HomeStar from './components/HomeStar.vue'
|
|||||||
import ExternalLink from './components/ExternalLink.vue'
|
import ExternalLink from './components/ExternalLink.vue'
|
||||||
import WwAds from './components/WwAds.vue'
|
import WwAds from './components/WwAds.vue'
|
||||||
import SpecialSponsor from './components/SpecialSponsor.vue'
|
import SpecialSponsor from './components/SpecialSponsor.vue'
|
||||||
|
import Banner from './components/Banner.vue'
|
||||||
import ElementPlus, { ElMessageBox } from 'element-plus'
|
import ElementPlus, { ElMessageBox } from 'element-plus'
|
||||||
import 'element-plus/dist/index.css'
|
import 'element-plus/dist/index.css'
|
||||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||||
@ -36,6 +37,7 @@ export default {
|
|||||||
...Theme,
|
...Theme,
|
||||||
Layout() {
|
Layout() {
|
||||||
return h(Theme.Layout, null, {
|
return h(Theme.Layout, null, {
|
||||||
|
'layout-top': () => h(Banner),
|
||||||
'home-hero-info-after':()=>h(HomeStar),
|
'home-hero-info-after':()=>h(HomeStar),
|
||||||
'home-hero-after': () => h(SpecialSponsor),
|
'home-hero-after': () => h(SpecialSponsor),
|
||||||
'home-features-after': () => h(HomeFriendly),
|
'home-features-after': () => h(HomeFriendly),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user