chore(admin): 优化PWA配置并更新资源文件
- 增强PWA开发环境支持,添加devOptions配置以便调试 - 优化Workbox缓存策略,排除大型SVG文件预缓存 - 添加运行时缓存规则支持SVG、图片等资源的CacheFirst策略 - 更新PWA manifest图标路径,使用public文件夹中的新图标资源 - 添加Service Worker注册文件和Workbox库文件 - 替换favicon.svg为favicon.ico,新增多种尺寸的PWA图标 - 生成PWA构建报告文件 - 优化manifest描述信息
@ -3,9 +3,9 @@
|
||||
|
||||
# VITE_SERVICE_BASE_URL=http://192.168.5.140:9999
|
||||
|
||||
# VITE_SERVICE_BASE_URL=https://api.qyzhjy.com
|
||||
VITE_SERVICE_BASE_URL=http://api.ydzx.qyzhjy.com
|
||||
|
||||
VITE_SERVICE_BASE_URL=https://172.16.10.130:5000
|
||||
# VITE_SERVICE_BASE_URL=https://172.16.10.130:5000
|
||||
|
||||
VITE_OTHER_SERVICE_BASE_URL= `{
|
||||
"demo": "http://localhost:9528"
|
||||
|
||||
@ -1,11 +1,24 @@
|
||||
import type { PluginOption } from 'vite'
|
||||
import { VitePWA } from 'vite-plugin-pwa'
|
||||
|
||||
export function setupPWA() {
|
||||
export function setupPWA(): PluginOption {
|
||||
return VitePWA({
|
||||
registerType: 'autoUpdate',
|
||||
// 添加 devOptions 以便在开发环境调试
|
||||
devOptions: {
|
||||
enabled: false, // 开发环境启用
|
||||
type: 'module',
|
||||
},
|
||||
workbox: {
|
||||
globPatterns: ['**/*.{js,css,html,ico,png,svg,webp,jpg,jpeg}'],
|
||||
// 排除大型 SVG 文件,避免预缓存
|
||||
globIgnores: [
|
||||
'**/team-title-bg*.svg',
|
||||
'**/cover-bg-active*.svg',
|
||||
'**/*-bg-animation*.svg',
|
||||
],
|
||||
globPatterns: ['**/*.{js,css,html,ico,png,webp,jpg,jpeg}', '**/*.svg'],
|
||||
runtimeCaching: [
|
||||
// 字体缓存
|
||||
{
|
||||
urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
|
||||
handler: 'CacheFirst',
|
||||
@ -17,34 +30,46 @@ export function setupPWA() {
|
||||
},
|
||||
},
|
||||
},
|
||||
// 大型 SVG 文件运行时缓存
|
||||
{
|
||||
urlPattern: /.*\.(svg)$/,
|
||||
handler: 'CacheFirst',
|
||||
options: {
|
||||
cacheName: 'svg-cache',
|
||||
expiration: {
|
||||
maxEntries: 50,
|
||||
maxAgeSeconds: 60 * 60 * 24 * 30, // 30 days
|
||||
},
|
||||
},
|
||||
},
|
||||
// 图片资源缓存
|
||||
{
|
||||
urlPattern: /.*\.(png|jpg|jpeg|webp|gif)$/,
|
||||
handler: 'CacheFirst',
|
||||
options: {
|
||||
cacheName: 'images-cache',
|
||||
expiration: {
|
||||
maxEntries: 100,
|
||||
maxAgeSeconds: 60 * 60 * 24 * 30, // 30 days
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
includeAssets: ['favicon.ico', 'apple-touch-icon.png', 'masked-icon.svg'],
|
||||
manifest: {
|
||||
name: '阅读之星竞赛系统',
|
||||
short_name: '阅读之星',
|
||||
description: '教育学会树人研究院阅读之星竞赛系统',
|
||||
description: '树人研究院阅读之星竞赛系统',
|
||||
theme_color: '#ffffff',
|
||||
background_color: '#ffffff',
|
||||
display: 'standalone',
|
||||
orientation: 'landscape',
|
||||
icons: [
|
||||
{
|
||||
src: 'pwa-192x192.png',
|
||||
sizes: '192x192',
|
||||
type: 'image/png',
|
||||
},
|
||||
{
|
||||
src: 'pwa-512x512.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png',
|
||||
},
|
||||
{
|
||||
src: 'pwa-512x512.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png',
|
||||
purpose: 'any maskable',
|
||||
},
|
||||
{ src: 'images/pwa_logo_192.png', sizes: '192x192', type: 'image/png' },
|
||||
{ src: 'images/pwa_logo_512.png', sizes: '512x512', type: 'image/png' },
|
||||
{ src: 'images/pwa_logo_512.png', sizes: '512x512', type: 'image/png', purpose: 'any' },
|
||||
{ src: 'images/pwa_logo_512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' },
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<html lang="zh-cmn-Hans">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.svg" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<title>%VITE_APP_TITLE%</title>
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
"dev": "vite --mode dev",
|
||||
"dev:prod": "vite --mode prod",
|
||||
"gen-route": "sa gen-route",
|
||||
"lint": "eslint . --fix",
|
||||
"lint": "eslint src --fix",
|
||||
"prepare": "simple-git-hooks",
|
||||
"preview": "vite preview",
|
||||
"release": "sa release",
|
||||
|
||||
BIN
apps/admin/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
@ -1,60 +0,0 @@
|
||||
<svg width="100%" height="100%" version="1.1" viewBox="0 0 1000 1000" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g>
|
||||
<path
|
||||
d="M 200,866 C 100,866 50,779.4 100,692.8 L 200,519.6 C 220,485 240,490 265,499.6 S 360,542.68 360,542.68 C 480.5,601 498,642.5 500,720 C 498,811 462,856 420,866"
|
||||
fill="url(#LinearGradient)" fill-rule="nonzero" opacity="1" stroke="none" />
|
||||
<path
|
||||
d="M 420,866 C 455,861 478,846 500,827 C 614,696 615,597 500,517 C 394,444 333,374 380,207.82 L 260,415.67 C 240.22,450 254.37,465.1 275.28,481.79 S 360,542.68 360,542.68 C 480.5,601 498,642.5 500,720 C 498,811 462,856 420,866"
|
||||
fill="url(#LinearGradient_2)" fill-rule="nonzero" opacity="1" stroke="none" />
|
||||
<path d="M 500,517 C 394,444 333,374 380,207.82 L 400,173.2 C 367,295 421,350 603,428 C 572,440 524,474 500,517"
|
||||
fill="url(#LinearGradient_3)" fill-rule="nonzero" opacity="1" stroke="none" />
|
||||
<path d="M 500,827 L 660,660 C 738,589 710,482 603,428 C 572,440 524,474 500,517 C 615,597 614,696 500,827"
|
||||
fill="url(#LinearGradient_4)" fill-rule="nonzero" opacity="1" stroke="none" />
|
||||
<path d="M 400,173.2 C 367,295 421,350 603,428 C 690,389, 750,445 788,500 L 600,173.2 C 550,86.6 450,86.6 400,173.2"
|
||||
fill="url(#LinearGradient_5)" fill-rule="nonzero" opacity="1" stroke="none" />
|
||||
<path
|
||||
d="M 500,827 L 660,660 C 738,589 710,482 603,428 C 690,389, 750,445 788,500 C 816,554 797,606 750,640 L 500,827"
|
||||
fill="url(#LinearGradient_6)" fill-rule="nonzero" opacity="1" stroke="none" />
|
||||
<path
|
||||
d="M 788,500 C 816,554 797,606 750,640 L 500,827 C 497,851 513,862 540,866 L 800,866 C 900,866 950,779.4 900,692.8 L 788,500"
|
||||
fill="url(#LinearGradient_7)" fill-rule="nonzero" opacity="1" stroke="none" />
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient gradientTransform="matrix(104.391 -73.3432 73.3432 104.391 277.441 710.122)"
|
||||
gradientUnits="userSpaceOnUse" id="LinearGradient" x1="0" x2="1" y1="0" y2="0">
|
||||
<stop offset="0" stop-color="#373ebf" />
|
||||
<stop offset="1" stop-color="#5058e6" />
|
||||
</linearGradient>
|
||||
<linearGradient gradientTransform="matrix(-173.747 557.324 -557.324 -173.747 508.829 258.172)"
|
||||
gradientUnits="userSpaceOnUse" id="LinearGradient_2" x1="0" x2="1" y1="0" y2="0">
|
||||
<stop offset="0" stop-color="#c2d6ff" />
|
||||
<stop offset="1" stop-color="#646cff" />
|
||||
</linearGradient>
|
||||
<linearGradient gradientTransform="matrix(157.951 295.666 -295.666 157.951 382.944 193.642)"
|
||||
gradientUnits="userSpaceOnUse" id="LinearGradient_3" x1="0" x2="1" y1="0" y2="0">
|
||||
<stop offset="0" stop-color="#5058e6" />
|
||||
<stop offset="1" stop-color="#373ebf" />
|
||||
</linearGradient>
|
||||
<linearGradient gradientTransform="matrix(-44.3023 219.578 -219.578 -44.3023 619.69 469.652)"
|
||||
gradientUnits="userSpaceOnUse" id="LinearGradient_4" x1="0" x2="1" y1="0" y2="0">
|
||||
<stop offset="0" stop-color="#91a7ff" />
|
||||
<stop offset="1" stop-color="#5058e6" />
|
||||
</linearGradient>
|
||||
<linearGradient gradientTransform="matrix(125.52 334.256 -334.256 125.52 539.723 235.139)"
|
||||
gradientUnits="userSpaceOnUse" id="LinearGradient_5" x1="0" x2="1" y1="0" y2="0">
|
||||
<stop offset="0" stop-color="#646cff" />
|
||||
<stop offset="1" stop-color="#c2d6ff" />
|
||||
</linearGradient>
|
||||
<linearGradient gradientTransform="matrix(-241.23 357.206 -357.206 -241.23 754.054 449.312)"
|
||||
gradientUnits="userSpaceOnUse" id="LinearGradient_6" x1="0" x2="1" y1="0" y2="0">
|
||||
<stop offset="0" stop-color="#c2d6ff" />
|
||||
<stop offset="1" stop-color="#646cff" />
|
||||
</linearGradient>
|
||||
<linearGradient gradientTransform="matrix(125.978 210.065 -210.065 125.978 596.433 613.665)"
|
||||
gradientUnits="userSpaceOnUse" id="LinearGradient_7" x1="0" x2="1" y1="0" y2="0">
|
||||
<stop offset="0" stop-color="#373ebf" />
|
||||
<stop offset="1" stop-color="#5058e6" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.8 KiB |
BIN
apps/admin/public/images/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
apps/admin/public/images/favicon.ico
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
apps/admin/public/images/favicon.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
1
apps/admin/public/images/nonsupport.svg
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
apps/admin/public/images/pwa_logo_192.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
apps/admin/public/images/pwa_logo_512.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
4950
apps/admin/report.html
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ export default readStarConfig(
|
||||
'**/public/**',
|
||||
'pnpm-lock.yaml',
|
||||
'**/*.md',
|
||||
'**/report.html',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||