feat(pwa): 实现PWA支持,支持离线访问

- 添加 manifest.json 文件配置PWA应用信息
- 创建 Service Worker (sw.js) 实现资源缓存与离线访问
- 在HTML中引入PWA相关meta标签及注册代码
- 更新项目文档结构说明,增加PWA相关文件描述
- 移除冗余CSS样式并优化页面加载逻辑
- 调整Google Analytics和51.LA统计脚本加载方式
- 完善部署文档中的PWA自定义配置说明
This commit is contained in:
hehh
2025-11-25 17:32:00 +08:00
parent ae249861fa
commit 8d46e85820
7 changed files with 219 additions and 97 deletions

View File

@@ -94,7 +94,7 @@ server {
## 项目结构规范 ## 项目结构规范
```textmate ```
Home/ Home/
├── index.html # 主页 ├── index.html # 主页
├── about.html # 关于页面 ├── about.html # 关于页面
@@ -102,6 +102,7 @@ Home/
├── DEPLOY.md # 部署与自定义指南 ├── DEPLOY.md # 部署与自定义指南
├── LICENSE # 开源许可证 ├── LICENSE # 开源许可证
├── CNAME # 自定义域名配置 ├── CNAME # 自定义域名配置
├── manifest.json # PWA 应用清单文件
├── vercel.json # Vercel 配置文件 ├── vercel.json # Vercel 配置文件
├── netlify.toml # Netlify 配置文件 ├── netlify.toml # Netlify 配置文件
├── _headers # Netlify 安全头配置 ├── _headers # Netlify 安全头配置
@@ -118,7 +119,7 @@ Home/
│ ├── main.js # 主页脚本 │ ├── main.js # 主页脚本
│ ├── about.js # 关于页面脚本 │ ├── about.js # 关于页面脚本
│ ├── bj.js # 背景效果脚本 │ ├── bj.js # 背景效果脚本
│ └── ... # 其他功能脚本 │ └── sw.js # Service WorkerPWA支持
├── data/ ├── data/
│ ├── articles.json # 文章数据 │ ├── articles.json # 文章数据
│ └── ... # 其他数据文件 │ └── ... # 其他数据文件
@@ -216,34 +217,10 @@ Home/
2. 在 HTML 元素上添加对应的数据属性 2. 在 HTML 元素上添加对应的数据属性
3. 更新语言切换逻辑 3. 更新语言切换逻辑
## TODO List ### PWA 自定义
1. 修改 `manifest.json` 文件来自定义应用名称、图标和主题色
### 已完成 2. 更新 `js/sw.js` 文件来调整缓存策略和缓存资源
- [x] 实现响应式布局,适配桌面、平板和手机设备 3. 在 HTML 文件中调整 PWA 相关的 meta 标签
- [x] 开发白天/夜间主题切换功能
- [x] 实现中英文国际化支持
- [x] 集成 Artalk 评论系统
- [x] 添加 GitHub 项目展示功能
- [x] 添加博客文章展示功能
- [x] 实现技术栈 3D 球体效果
- [x] 添加一键部署到 Vercel、Netlify 等平台的支持
- [x] 实现移动端优化,包括可拖拽悬浮按钮
- [x] 添加骨架屏加载效果
- [x] 实现数据缓存机制,提升页面加载速度
- [x] 添加淡入动画效果,提升用户体验
- [x] 添加网站统计功能, 如不蒜子、百度统计、Google Analytics、51.LA
### 待完成
- [ ] 添加更多数据源(如 Twitter、知乎等
- [ ] 实现技术栈标签的交互功能(点击跳转链接等)
- [ ] 添加文章详情页与分页列表功能
- [ ] 实现更多的个性化定制选项
- [ ] 添加键盘快捷键支持
- [ ] 增加更多动画效果和交互细节
- [ ] 实现 PWA 支持,支持离线访问
- [ ] 添加更多主题选项(如高对比度模式等)
- [ ] 实现深色模式下的图片优化处理
- [ ] 添加无障碍访问支持ARIA 属性完善)
--- ---

View File

@@ -31,6 +31,7 @@
- 📊 **网站统计**集成多种统计服务不蒜子、百度统计、Google Analytics、51.LA统一配置管理 - 📊 **网站统计**集成多种统计服务不蒜子、百度统计、Google Analytics、51.LA统一配置管理
- 🌗 **主题与语言**:一键切换 Day/Night 与 CN/EN自动缓存记忆 - 🌗 **主题与语言**:一键切换 Day/Night 与 CN/EN自动缓存记忆
-**性能与体验**:缓存字段精简、骨架占位与淡入过渡、异步抓取避免阻塞 -**性能与体验**:缓存字段精简、骨架占位与淡入过渡、异步抓取避免阻塞
- 📱 **PWA 支持**:支持安装为本地应用,离线访问核心功能
## 🚀 一键部署 ## 🚀 一键部署
@@ -79,7 +80,7 @@
## 📁 项目结构 ## 📁 项目结构
```textmate ```
Home/ Home/
├── index.html # 主页 ├── index.html # 主页
├── about.html # 关于页面 ├── about.html # 关于页面
@@ -87,6 +88,7 @@ Home/
├── DEPLOY.md # 部署与自定义指南 ├── DEPLOY.md # 部署与自定义指南
├── LICENSE # 开源许可证 ├── LICENSE # 开源许可证
├── CNAME # 自定义域名配置 ├── CNAME # 自定义域名配置
├── manifest.json # PWA 应用清单文件
├── vercel.json # Vercel 配置文件 ├── vercel.json # Vercel 配置文件
├── netlify.toml # Netlify 配置文件 ├── netlify.toml # Netlify 配置文件
├── _headers # Netlify 安全头配置 ├── _headers # Netlify 安全头配置
@@ -103,7 +105,7 @@ Home/
│ ├── main.js # 主页脚本 │ ├── main.js # 主页脚本
│ ├── about.js # 关于页面脚本 │ ├── about.js # 关于页面脚本
│ ├── bj.js # 背景效果脚本 │ ├── bj.js # 背景效果脚本
│ └── ... # 其他功能脚本 │ └── sw.js # Service WorkerPWA支持
├── data/ ├── data/
│ ├── articles.json # 文章数据 │ ├── articles.json # 文章数据
│ └── ... # 其他数据文件 │ └── ... # 其他数据文件
@@ -137,7 +139,7 @@ Home/
## 🚀 快速开始 ## 🚀 快速开始
```shell ``shell
# 克隆项目 # 克隆项目
git clone https://github.com/listener-He/Home.git git clone https://github.com/listener-He/Home.git
@@ -179,7 +181,7 @@ open about.html
- [x] 添加骨架屏加载效果 - [x] 添加骨架屏加载效果
- [x] 实现数据缓存机制,提升页面加载速度 - [x] 实现数据缓存机制,提升页面加载速度
- [x] 添加淡入动画效果,提升用户体验 - [x] 添加淡入动画效果,提升用户体验
- [x] 添加网站统计功能, 如不蒜子、百度统计、Google Analytics、51.LA - [x] 实现 PWA 支持,支持离线访问
### 待完成 ### 待完成
- [ ] 添加更多数据源(如 Twitter、知乎等 - [ ] 添加更多数据源(如 Twitter、知乎等
@@ -188,7 +190,6 @@ open about.html
- [ ] 实现更多的个性化定制选项 - [ ] 实现更多的个性化定制选项
- [ ] 添加键盘快捷键支持 - [ ] 添加键盘快捷键支持
- [ ] 增加更多动画效果和交互细节 - [ ] 增加更多动画效果和交互细节
- [ ] 实现 PWA 支持,支持离线访问
- [ ] 添加更多主题选项(如高对比度模式等) - [ ] 添加更多主题选项(如高对比度模式等)
- [ ] 实现深色模式下的图片优化处理 - [ ] 实现深色模式下的图片优化处理
- [ ] 添加无障碍访问支持ARIA 属性完善) - [ ] 添加无障碍访问支持ARIA 属性完善)

View File

@@ -5,6 +5,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>关于我 - Honesty</title> <title>关于我 - Honesty</title>
<meta http-equiv="X-UA-Compatible" content="ie=edge"> <meta http-equiv="X-UA-Compatible" content="ie=edge">
<!-- PWA相关配置 -->
<meta name="theme-color" content="#6c5ce7">
<link rel="manifest" href="/manifest.json">
<!--SEO信息 --> <!--SEO信息 -->
<meta name="description" content="关于Honesty,关于HeHouHui,关于HeHui,关于明厚, About Me Honesty, About Me HeHouHui, About Me HeHui"> <meta name="description" content="关于Honesty,关于HeHouHui,关于HeHui,关于明厚, About Me Honesty, About Me HeHouHui, About Me HeHui">
<meta name="keywords" content="Honesty,HeHouHui,HeHui,明厚"> <meta name="keywords" content="Honesty,HeHouHui,HeHui,明厚">
@@ -41,6 +45,8 @@
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/artalk/2.9.1/Artalk.css"> <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/artalk/2.9.1/Artalk.css">
<link rel="stylesheet" href="css/artalk.css?version=20251125"> <link rel="stylesheet" href="css/artalk.css?version=20251125">
<link rel="icon" href="favicon.ico"> <link rel="icon" href="favicon.ico">
<link rel="apple-touch-icon" href="./images/logo.png">
<!--IE淘汰计划--> <!--IE淘汰计划-->
<script> <script>
if (/*@cc_on!@*/false || (!!window.MSInputMethodContext && !!document.documentMode)) window.location.href = "https://imsyy.top/upgrade-your-browser/index.html?referrer=" + encodeURIComponent(window.location.href); if (/*@cc_on!@*/false || (!!window.MSInputMethodContext && !!document.documentMode)) window.location.href = "https://imsyy.top/upgrade-your-browser/index.html?referrer=" + encodeURIComponent(window.location.href);
@@ -424,5 +430,34 @@
initFormatter(); initFormatter();
} }
</script> </script>
<!-- PWA注册 -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/js/sw.js')
.then(function(registration) {
console.log('SW registered: ', registration);
})
.catch(function(registrationError) {
console.log('SW registration failed: ', registrationError);
});
});
}
</script>
<!-- Apple PWA支持 -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Honesty">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<!-- Windows PWA支持 -->
<meta name="msapplication-TileImage" content="/images/avatar.jpeg">
<meta name="msapplication-TileColor" content="#6c5ce7">
<meta name="msapplication-tap-highlight" content="no">
<!-- 其他PWA相关meta标签 -->
<meta name="mobile-web-app-capable" content="yes">
</body> </body>
</html> </html>

View File

@@ -1076,14 +1076,7 @@ body {
black 90%, black 90%,
transparent 100% transparent 100%
); );
-webkit-mask-image: linear-gradient(
to right,
transparent 0%,
black 10%,
black 90%,
transparent 100%
);
/* 行内滚动动画由 .tech-row 控制 */
} }
.tech-row { .tech-row {
display: flex; display: flex;
@@ -2231,7 +2224,6 @@ body {
gap: 15px; gap: 15px;
margin-top: 1rem; margin-top: 1rem;
} }
}
.interest-item { .interest-item {
background: rgba(128, 128, 128, 0.05); background: rgba(128, 128, 128, 0.05);
@@ -2780,13 +2772,6 @@ body {
black 90%, black 90%,
transparent 100% transparent 100%
); );
-webkit-mask-image: linear-gradient(
to right,
transparent 0%,
black 20%,
black 80%,
transparent 100%
);
} }
.tech-row { .tech-row {
display: flex; display: flex;

View File

@@ -13,13 +13,16 @@
<meta name="keywords" content="Honesty,HeHouHui,HeHui,明厚"> <meta name="keywords" content="Honesty,HeHouHui,HeHui,明厚">
<meta name="author" content="Honesty"> <meta name="author" content="Honesty">
<!-- PWA相关配置 -->
<meta name="theme-color" content="#6c5ce7">
<link rel="manifest" href="/manifest.json">
<!-- 社交平台分享优化 --> <!-- 社交平台分享优化 -->
<!-- Open Graph / Facebook --> <!-- Open Graph / Facebook -->
<meta property="og:type" content="website"> <meta property="og:type" content="website">
<meta property="og:url" content="https://www.hehouhui.cn/"> <meta property="og:url" content="https://www.hehouhui.cn/">
<meta property="og:title" content="Honesty的主页 - Java后端 & AI工程师"> <meta property="og:title" content="Honesty的主页 - Java后端 & AI工程师">
<meta property="og:description" <meta property="og:description" content="我是Honesty一名充满热情的Java后端开发工程师专注于AI技术的探索与应用。来自湖南现在上海工作享受在这座充满活力的城市中追求技术梦想。">
content="我是Honesty一名充满热情的Java后端开发工程师专注于AI技术的探索与应用。来自湖南现在上海工作享受在这座充满活力的城市中追求技术梦想。">
<meta property="og:image" content="images/avatar.jpeg"> <meta property="og:image" content="images/avatar.jpeg">
<meta property="og:site_name" content="Honesty的主页"> <meta property="og:site_name" content="Honesty的主页">
@@ -27,16 +30,14 @@
<meta property="twitter:card" content="summary_large_image"> <meta property="twitter:card" content="summary_large_image">
<meta property="twitter:url" content="https://www.hehouhui.cn/"> <meta property="twitter:url" content="https://www.hehouhui.cn/">
<meta property="twitter:title" content="Honesty的主页 - Java后端 & AI工程师"> <meta property="twitter:title" content="Honesty的主页 - Java后端 & AI工程师">
<meta property="twitter:description" <meta property="twitter:description" content="我是Honesty一名充满热情的Java后端开发工程师专注于AI技术的探索与应用。来自湖南现在上海工作享受在这座充满活力的城市中追求技术梦想。">
content="我是Honesty一名充满热情的Java后端开发工程师专注于AI技术的探索与应用。来自湖南现在上海工作享受在这座充满活力的城市中追求技术梦想。">
<meta property="twitter:image" content="https://www.hehouhui.cn/images/avatar.jpeg"> <meta property="twitter:image" content="https://www.hehouhui.cn/images/avatar.jpeg">
<meta property="twitter:site" content="@Honesty861024"> <meta property="twitter:site" content="@Honesty861024">
<!-- 微信小程序/朋友圈分享 --> <!-- 微信小程序/朋友圈分享 -->
<meta property="wechat:image" content="https://www.hehouhui.cn/images/avatar.jpeg"> <meta property="wechat:image" content="https://www.hehouhui.cn/images/avatar.jpeg">
<meta property="wechat:title" content="Honesty的主页 - Java后端 & AI工程师"> <meta property="wechat:title" content="Honesty的主页 - Java后端 & AI工程师">
<meta property="wechat:description" <meta property="wechat:description" content="我是Honesty一名充满热情的Java后端开发工程师专注于AI技术的探索与应用。">
content="我是Honesty一名充满热情的Java后端开发工程师专注于AI技术的探索与应用。">
<title>Honesty的主页</title> <title>Honesty的主页</title>
<link rel="stylesheet" type="text/css" href="./css/style.css?version=3"> <link rel="stylesheet" type="text/css" href="./css/style.css?version=3">
@@ -57,6 +58,7 @@
media="all"> media="all">
<!--IE淘汰计划--> <!--IE淘汰计划-->
<script> <script>
if (/*@cc_on!@*/false || (!!window.MSInputMethodContext && !!document.documentMode)) window.location.href = "https://imsyy.top/upgrade-your-browser/index.html?referrer=" + encodeURIComponent(window.location.href); if (/*@cc_on!@*/false || (!!window.MSInputMethodContext && !!document.documentMode)) window.location.href = "https://imsyy.top/upgrade-your-browser/index.html?referrer=" + encodeURIComponent(window.location.href);
@@ -375,43 +377,63 @@
<!-- Google Analytics --> <!-- Google Analytics -->
<script> <script>
// 定义 gtag 函数 // 定义 gtag 函数作为安全兜底
<script type="text/javascript">
try {
window.dataLayer = window.dataLayer || []; window.dataLayer = window.dataLayer || [];
function gtag() { function gtag() {
dataLayer.push(arguments); dataLayer.push(arguments);
} }
gtag('js', new Date()); gtag('js', new Date());
} catch (e) {
console.log("Google Analytics Init 错误", e);
}
// 异步加载 Google Analytics
const gaScript = document.createElement('script');
gaScript.async = true;
gaScript.src = `${SiteConfig.analytics.google.src}?id=${SiteConfig.analytics.google.id}`;
document.head.appendChild(gaScript);
gaScript.onload = function () {
gtag('config', SiteConfig.analytics.google.id);
};
</script> </script>
<!-- 51.LA统计 --> <!-- 再异步加载 Google 的 gtag.js -->
<script> <script async src="https://www.googletagmanager.com/gtag/js?id=G-DYWDEVKDP0"></script>
<!-- 可选:继续调用 config -->
<script type="text/javascript">
try { try {
!function (p) { gtag('config', 'G-DYWDEVKDP0');
"use strict";
!function (t) {
var s = window, e = document, i = p,
c = "".concat("https:" === e.location.protocol ? "https://" : "http://", "sdk.51.la/js-sdk-pro.min.js"),
n = e.createElement("script"), r = e.getElementsByTagName("script")[0];
n.type = "text/javascript", n.setAttribute("charset", "UTF-8"), n.async = !0, n.src = c, n.id = "LA_COLLECT", i.d = n;
var o = function () {
s.LA.ids.push(i)
};
s.LA ? s.LA.ids && o() : (s.LA = p, s.LA.ids = [], o()), r.parentNode.insertBefore(n, r)
}()
}({id: SiteConfig.analytics.tencent.id, ck: SiteConfig.analytics.tencent.ck});
} catch (e) { } catch (e) {
console.log("51.la统计错误", e); console.error("Google Analytics config 失败", e);
} }
</script> </script>
<script>
!function(p){"use strict";!function(t){var s=window,e=document,i=p,c="".concat("https:"===e.location.protocol?"https://":"http://","sdk.51.la/js-sdk-pro.min.js"),n=e.createElement("script"),r=e.getElementsByTagName("script")[0];n.type="text/javascript",n.setAttribute("charset","UTF-8"),n.async=!0,n.src=c,n.id="LA_COLLECT",i.d=n;var o=function(){s.LA.ids.push(i)};s.LA?s.LA.ids&&o():(s.LA=p,s.LA.ids=[],o()),r.parentNode.insertBefore(n,r)}()}({id:"3OBGjwDdEIRS7XZ1",ck:"3OBGjwDdEIRS7XZ1"});
</script>
<!-- PWA注册 -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/js/sw.js')
.then(function(registration) {
console.log('SW registered: ', registration);
})
.catch(function(registrationError) {
console.log('SW registration failed: ', registrationError);
});
});
}
</script>
<!-- Apple PWA支持 -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Honesty">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<!-- Windows PWA支持 -->
<meta name="msapplication-TileImage" content="/images/avatar.jpeg">
<meta name="msapplication-TileColor" content="#6c5ce7">
<meta name="msapplication-tap-highlight" content="no">
<!-- 其他PWA相关meta标签 -->
<meta name="mobile-web-app-capable" content="yes">
</body>
</html>

76
js/sw.js Normal file
View File

@@ -0,0 +1,76 @@
// Service Worker for PWA
const CACHE_NAME = 'honesty-home-v1.0.0';
const urlsToCache = [
'/',
'/index.html',
'/about.html',
'/css/style.css',
'/css/about.css',
'/css/artalk.css',
'/js/config.js',
'/js/main.js',
'/js/about.js'
];
// 安装事件 - 缓存资源
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
// 获取事件 - 拦截网络请求
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// 如果在缓存中找到响应,则返回缓存的资源
if (response) {
return response;
}
// 克隆请求,因为请求是一个流,只能被消费一次
const fetchRequest = event.request.clone();
// 如果没有在缓存中找到,则发起网络请求
return fetch(fetchRequest).then(response => {
// 检查响应是否有效
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// 克隆响应,因为响应是一个流,只能被消费一次
const responseToCache = response.clone();
// 打开缓存并将响应添加到缓存中
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseToCache);
});
return response;
});
})
);
});
// 激活事件 - 清理旧缓存
self.addEventListener('activate', event => {
const cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});

26
manifest.json Normal file
View File

@@ -0,0 +1,26 @@
{
"name": "Honesty的个人主页",
"short_name": "Honesty",
"description": "我是Honesty一名充满热情的Java后端开发工程师专注于AI技术的探索与应用。",
"start_url": "/",
"display": "standalone",
"background_color": "#f3f4f6",
"theme_color": "#6c5ce7",
"icons": [
{
"src": "/images/avatar.jpeg",
"sizes": "192x192",
"type": "image/jpeg"
},
{
"src": "/images/avatar.jpeg",
"sizes": "512x512",
"type": "image/jpeg"
},
{
"src": "/apple-touch-icon.png",
"sizes": "180x180",
"type": "image/png"
}
]
}