chore(admin): 优化PWA配置并更新资源文件

- 增强PWA开发环境支持,添加devOptions配置以便调试
- 优化Workbox缓存策略,排除大型SVG文件预缓存
- 添加运行时缓存规则支持SVG、图片等资源的CacheFirst策略
- 更新PWA manifest图标路径,使用public文件夹中的新图标资源
- 添加Service Worker注册文件和Workbox库文件
- 替换favicon.svg为favicon.ico,新增多种尺寸的PWA图标
- 生成PWA构建报告文件
- 优化manifest描述信息
This commit is contained in:
2026-03-16 10:55:53 +08:00
parent 40a6850c3f
commit 782c61a38c
15 changed files with 5121 additions and 91 deletions

View File

@ -1,6 +1,6 @@
<script lang="ts" setup>
import VScaleScreen from 'v-scale-screen'
import { computed, onMounted, ref } from 'vue'
import { computed, onMounted, ref, watch } from 'vue'
import _defaultBg from '@/assets/imgs/user/home-bg.jpg'
import titleBg2 from '@/assets/imgs/user/title-bg2.webp'
import _defaultTitleBg from '@/assets/imgs/user/title-bg.webp'
@ -67,6 +67,9 @@ const titleBgUrlMapping: Record<number, string> = {
const { routerPushByKey } = useRouterPush()
const titleBgUrl = computed(() => titleBgUrlMapping[props.titleBgType] || _defaultBg)
const titleBgLoaded = ref(false)
const currentTitleBg = ref('')
const imageLoadStatus = ref<Record<string, boolean>>({})
// const router = useRouter()
const innerBgUrl = ref(_defaultBg)
@ -110,10 +113,62 @@ function handleBackHome() {
}
onMounted(() => {
fetchSystemConfig()
setTimeout(() => {
showContent.value = true
}, 100)
// 预加载所有标题背景图,并跟踪加载状态
const preloadAllImages = () => {
const promises = Object.entries(titleBgUrlMapping).map(([_key, url]) => {
return new Promise((resolve) => {
const img = new Image()
img.onload = () => {
imageLoadStatus.value[url] = true
resolve(true)
}
img.onerror = () => {
imageLoadStatus.value[url] = false
resolve(false)
}
img.src = url
})
})
// 也预加载默认背景
const defaultBgPromise = new Promise((resolve) => {
const img = new Image()
img.onload = () => {
imageLoadStatus.value[_defaultBg] = true
resolve(true)
}
img.onerror = () => {
imageLoadStatus.value[_defaultBg] = false
resolve(false)
}
img.src = _defaultBg
})
return Promise.all([...promises, defaultBgPromise])
}
// 开始预加载
preloadAllImages()
.then(() => {
// 所有图片预加载完成后,设置当前背景
currentTitleBg.value = titleBgUrl.value
titleBgLoaded.value = true
fetchSystemConfig()
setTimeout(() => {
showContent.value = true
}, 100)
})
.catch(() => {
// 即使预加载失败也继续
currentTitleBg.value = titleBgUrl.value
titleBgLoaded.value = true
fetchSystemConfig()
setTimeout(() => {
showContent.value = true
}, 100)
})
// 延迟显示内容,等待标题卷轴展开
const delay = props.showTitle ? 500 : 100
@ -126,6 +181,28 @@ onMounted(() => {
showButton.value = true
}, delay + 500)
})
// 监听 titleBgUrl 变化,确保图片切换平滑
watch(titleBgUrl, (newUrl) => {
if (newUrl && imageLoadStatus.value[newUrl]) {
// 如果图片已经加载过,直接切换
currentTitleBg.value = newUrl
}
else if (newUrl) {
// 如果图片还没加载,预加载它
const img = new Image()
img.onload = () => {
imageLoadStatus.value[newUrl] = true
currentTitleBg.value = newUrl
}
img.onerror = () => {
imageLoadStatus.value[newUrl] = false
// 加载失败时使用默认背景
currentTitleBg.value = _defaultBg
}
img.src = newUrl
}
})
</script>
<template>
@ -146,7 +223,22 @@ onMounted(() => {
<section v-if="showTitle" class="title-section">
<Transition name="scroll-unfold" appear>
<div v-if="showContent" class="title-wrapper">
<div class="scroll-bg" :style="{ backgroundImage: `url(${titleBgUrl})` }">
<div class="scroll-bg">
<!-- 预渲染所有背景图 opacity 切换避免加载抖动 -->
<img
v-for="(url, _key) in titleBgUrlMapping"
:key="_key"
:src="url"
class="scroll-bg-img"
:class="{
active: titleBgUrl === url && imageLoadStatus[url] !== false,
loading: imageLoadStatus[url] === undefined,
}"
:style="{ visibility: imageLoadStatus[url] === false ? 'hidden' : 'visible' }"
aria-hidden="true"
@load="imageLoadStatus[url] = true"
@error="imageLoadStatus[url] = false"
>
<Transition name="fade-in-delay" appear>
<h1 v-if="showContent" class="main-title">
{{ title }}
@ -221,14 +313,33 @@ onMounted(() => {
width: 400px !important; /* Fixed width to prevent squishing during animation */
height: 155px !important;
position: relative;
background: no-repeat center center;
background-size: contain;
display: flex;
align-items: center;
justify-content: center;
padding-bottom: 10px;
flex-shrink: 0; /* Prevent shrinking */
.scroll-bg-img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: contain;
opacity: 0;
transition: opacity 0.5s ease;
pointer-events: none;
&.active {
opacity: 1;
}
&.loading {
opacity: 0;
filter: blur(2px);
}
}
.main-title {
font-size: 2.5rem;
color: $text-color;
@ -236,6 +347,8 @@ onMounted(() => {
letter-spacing: 4px;
text-align: center;
white-space: nowrap; /* Prevent text wrapping */
position: relative;
z-index: 1;
}
}
}