From ae1bcf555dbec1017c13a8ca55414e2cd1aadf0f Mon Sep 17 00:00:00 2001 From: Han <1655466387@qq.com> Date: Sun, 9 Mar 2025 12:00:36 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=BD=AE=E9=A1=B6?= =?UTF-8?q?=E6=96=87=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 4 +-- src/components/ArticleCard.astro | 2 +- src/content.config.ts | 3 +- src/pages/[...page].astro | 6 ++-- src/styles/Article.less | 9 +++++- src/styles/components/ArticleCard.less | 42 ++++++++++++++++++++++++++ src/styles/components/Search.less | 3 +- src/utils/getCover.ts | 6 ++-- src/utils/getFileTime.ts | 17 ----------- src/utils/getPostInfo.ts | 4 +-- src/utils/moveTopToFirst.ts | 8 +++++ src/utils/vhSearch.ts | 6 ++-- 12 files changed, 75 insertions(+), 35 deletions(-) delete mode 100644 src/utils/getFileTime.ts create mode 100644 src/utils/moveTopToFirst.ts diff --git a/package.json b/package.json index ddf4302..77abcec 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,8 @@ "@astrojs/sitemap": "^3.2.1", "@swup/astro": "^1.5.0", "aplayer": "^1.10.1", - "astro": "^5.4.1", - "lenis": "^1.2.1", + "astro": "^5.4.2", + "lenis": "^1.2.3", "overlayscrollbars": "^2.11.1", "vanilla-lazyload": "^19.1.3", "vh-plugin": "^1.2.2" diff --git a/src/components/ArticleCard.astro b/src/components/ArticleCard.astro index 2cb3dcc..937e1ae 100644 --- a/src/components/ArticleCard.astro +++ b/src/components/ArticleCard.astro @@ -12,7 +12,7 @@ import "../styles/components/ArticleCard.less"; ---
- +
{post.data.title}
diff --git a/src/content.config.ts b/src/content.config.ts index a2eb10f..9f2d71e 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -14,7 +14,8 @@ const blog = defineCollection({ id: z.union([z.string(), z.number()]), cover: z.string().optional(), recommend: z.boolean().optional(), - hide: z.boolean().optional() + hide: z.boolean().optional(), + top: z.boolean().optional() }), }); diff --git a/src/pages/[...page].astro b/src/pages/[...page].astro index d6e766d..663eafd 100644 --- a/src/pages/[...page].astro +++ b/src/pages/[...page].astro @@ -2,12 +2,14 @@ import { getCollection } from "astro:content"; import type { GetStaticPathsOptions } from "astro"; import setSearchJson from "../utils/vhSearch"; +import moveTopToFirst from "../utils/moveTopToFirst"; export async function getStaticPaths(options: GetStaticPathsOptions) { const { paginate } = options; const posts = (await getCollection("blog")).sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()); - // 生成 Search JSON 文件 ====== + // 置顶文章功能 + moveTopToFirst(posts); + // 生成 Search JSON 文件 await setSearchJson(posts); - // 生成 Search JSON 文件 ====== // 隐藏的文章不显示 return paginate( posts.filter(i => !i.data.hide), diff --git a/src/styles/Article.less b/src/styles/Article.less index 4dd24f5..a8532bb 100644 --- a/src/styles/Article.less +++ b/src/styles/Article.less @@ -3,7 +3,6 @@ section.vh-container { flex: 1; min-width: 0; - &>header { box-sizing: border-box; padding: 2rem 1rem 1rem; @@ -70,6 +69,14 @@ section.vh-container { box-shadow: 0 12px 8px 6px rgba(7, 17, 27, 0.05); overflow: hidden; + .aplayer-list { + max-height: max-content !important; + + &>ol { + max-height: 566px !important; + } + } + &>.tag-list { box-sizing: border-box; padding-top: 2rem; diff --git a/src/styles/components/ArticleCard.less b/src/styles/components/ArticleCard.less index 824d8fa..dec9a9a 100644 --- a/src/styles/components/ArticleCard.less +++ b/src/styles/components/ArticleCard.less @@ -20,6 +20,7 @@ section.article-list { overflow: hidden; &>a.vh-article-link { + position: relative; box-sizing: border-box; display: flex; flex-direction: column; @@ -38,7 +39,48 @@ section.article-list { } } + &.active { + &::before { + content: "置顶"; + position: absolute; + top: 1.6rem; + left: 0.66rem; + box-sizing: border-box; + padding: 0.18rem 1rem; + width: max-content; + height: max-content; + font-size: 1rem; + color: #fff; + border-radius: 0.66rem; + transform: rotate(-45deg); + /* 确保元素有半透明背景 */ + background: #00000033; + box-shadow: 0 2rem 1rem rgba(0, 0, 0, 0.1); + z-index: 1; + /* 高斯模糊效果 */ + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); + /* 兼容旧版浏览器 */ + } + + &>.vh-article-banner { + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.06); + z-index: 1; + pointer-events: none; + } + } + } + + &>.vh-article-banner { + position: relative; flex-shrink: 0; box-sizing: border-box; width: 100%; diff --git a/src/styles/components/Search.less b/src/styles/components/Search.less index fd623e3..9654d06 100644 --- a/src/styles/components/Search.less +++ b/src/styles/components/Search.less @@ -14,11 +14,12 @@ transition: opacity 0.16s ease-in-out; opacity: 0; pointer-events: none; - z-index: 10; + z-index: -1; &.active { opacity: 1; pointer-events: auto; + z-index: 1; &>main { transform: translateY(0); diff --git a/src/utils/getCover.ts b/src/utils/getCover.ts index f18b856..a40e035 100644 --- a/src/utils/getCover.ts +++ b/src/utils/getCover.ts @@ -59,10 +59,8 @@ async function* createImageIterator(dir: string) { const targetDir = path.resolve(__dirname, '../../public/assets/images/banner/'); // 目标目录 const fileIter = createImageIterator(targetDir); -const getCover = async (filename: string | null | undefined) => { +export default async (filename: string | null | undefined) => { if (filename) return filename; const { value } = await fileIter.next(); return SITE_INFO.Site + `/assets/images/banner/${value}` -} - -export default getCover; \ No newline at end of file +} \ No newline at end of file diff --git a/src/utils/getFileTime.ts b/src/utils/getFileTime.ts deleted file mode 100644 index fd67b50..0000000 --- a/src/utils/getFileTime.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { execSync } from "child_process"; - -const COMMITER_DATE_ISO_FORMAT = "%cI"; - -export default function getFileCreateTime(filePath: string, type: string): Date { - try { - const result = execSync(`git log --pretty=format:"${COMMITER_DATE_ISO_FORMAT}" ${filePath}`).toString(); - if (!result) { - throw new Error('Git command failed to execute'); - } - const commits = result.split(/\r?\n/g); - return new Date(commits[type == 'create' ? commits.length - 1 : 0]); - } catch (e) { - // not committed yet - return new Date(); - } -} \ No newline at end of file diff --git a/src/utils/getPostInfo.ts b/src/utils/getPostInfo.ts index a0a6e46..758a219 100644 --- a/src/utils/getPostInfo.ts +++ b/src/utils/getPostInfo.ts @@ -24,8 +24,8 @@ const getTags = () => { // 获取推荐文章 (给文章添加 recommend: true 字段) const getRecommendArticles = () => { - const recommendList = posts.filter(i => i.data.recommend).slice(0, 6); - return (recommendList.length ? recommendList : posts).slice(0, 6).map(async i => ({ title: i.data.title, date: i.data.date, id: i.data.id, cover: await getCover(i.data.cover) })) + const recommendList = posts.filter(i => i.data.recommend); + return (recommendList.length ? recommendList : posts.slice(0, 6)).map(async i => ({ title: i.data.title, date: i.data.date, id: i.data.id, cover: await getCover(i.data.cover) })) }; export { getCategories, getTags, getRecommendArticles }; \ No newline at end of file diff --git a/src/utils/moveTopToFirst.ts b/src/utils/moveTopToFirst.ts new file mode 100644 index 0000000..c5f583f --- /dev/null +++ b/src/utils/moveTopToFirst.ts @@ -0,0 +1,8 @@ +export default (arr: Array) => { + const index = arr.findIndex((item: any) => item.data.top === true); + if (index !== -1) { + const [item] = arr.splice(index, 1); + arr.unshift(item); + } + return arr; +} \ No newline at end of file diff --git a/src/utils/vhSearch.ts b/src/utils/vhSearch.ts index 4cdb390..93ddc75 100644 --- a/src/utils/vhSearch.ts +++ b/src/utils/vhSearch.ts @@ -2,7 +2,7 @@ import fs from 'fs/promises'; import path from 'path'; import * as cheerio from 'cheerio'; -const setSearchJson = async (posts: any[]) => { +export default async (posts: any[]) => { const searchIndex = posts.map(i => { const $ = cheerio.load(`${i.rendered.html}`); return { @@ -25,6 +25,4 @@ const setSearchJson = async (posts: any[]) => { } catch (error) { console.error('Error writing search index file:', error); } -}; - -export default setSearchJson; \ No newline at end of file +}; \ No newline at end of file From 60c91c59893596584f720c73a2eed2a70b48e979 Mon Sep 17 00:00:00 2001 From: Han <1655466387@qq.com> Date: Sun, 9 Mar 2025 12:00:49 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E4=BC=98=E5=8C=96Lenis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/scripts/Lenis.ts | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/scripts/Lenis.ts b/src/scripts/Lenis.ts index 9213453..c0bbc21 100644 --- a/src/scripts/Lenis.ts +++ b/src/scripts/Lenis.ts @@ -1,8 +1,31 @@ import Lenis from "lenis"; -const IncludeClassName = ['vh-code-box', 'vh-search-list']; +const IncludeClassName = ['.vh-search.active .vh-search-list', '.aplayer-list>ol', '.vh-code-box>pre']; +let scrollStatus: any = null; +let clearTimer: any = null; (window as any).vhlenis = new Lenis({ - prevent: (node) => IncludeClassName.some((i: string) => node.className.includes(i)) + prevent: (node) => { + const res = IncludeClassName.some((i: string) => { + if (node.querySelector('main') || (node.tagName.toLowerCase() === 'main' && !node.parentElement!.classList.contains('active'))) return null; + scrollStatus = node.querySelector(i) ? node : null; + if (i.includes('vh-code-box>pre')) { + try { + scrollStatus = (node.querySelector(i)!.clientHeight == 888) ? node : null; + if (node.querySelector(i)!.clientHeight == 888) { + clearTimeout(clearTimer); + (window as any).vhlenis.stop(); + clearTimer = setTimeout(() => { + (window as any).vhlenis.start(); + }, 566); + } + } catch { + scrollStatus = null; + } + } + return scrollStatus; + }) + return res; + } }); const lenisInit = (time: any) => { (window as any).vhlenis.raf(time) From 0b158b7e7a7009c8573b8bed3363f23aaf4e032e Mon Sep 17 00:00:00 2001 From: Han <1655466387@qq.com> Date: Sun, 9 Mar 2025 12:18:18 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=B9=B6=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E9=98=BB=E5=B0=BC=E6=BB=9A=E5=8A=A8=E5=BC=80=E5=85=B3?= =?UTF-8?q?=E2=9C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 +++++ src/config.ts | 2 ++ src/scripts/BackTop.ts | 2 +- src/scripts/Init.ts | 6 ++---- src/scripts/Lenis.ts | 2 +- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f70201a..356dbca 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ - [x] 简洁的响应式设计 - [x] 流畅的动画和页面过渡 +- [x] 丝滑的阻尼滚动效果(自定义开启/关闭) - [x] 两列布局 - [x] 阅读时间 - [x] 字数统计 @@ -27,9 +28,12 @@ - [x] 分类 - [x] 归档 - [x] 动态 +- [x] 圈子 - [x] 关于 +- [x] 留言板 - [x] 友情链接 - [x] 推荐文章 +- [x] 置顶文章 - [x] 谷歌广告 - [x] 内置 404 页面 - [x] Sitemap 支持 @@ -114,6 +118,7 @@ date: 文章创建日期 updated: 文章更新日期 cover: "封面图URL (为空默认随机内置封面 /public/assets/images/banner)" recommend: false # 是否推荐文章 +top: false # 是否置顶文章 hide: false # 是否隐藏文章 --- ``` diff --git a/src/config.ts b/src/config.ts index 3c9043f..be1b321 100644 --- a/src/config.ts +++ b/src/config.ts @@ -50,4 +50,6 @@ export default { // 文章页广告(不填不开启) articleAD_Slot: `` }, + // 阻尼滚动效果开关 + LenisScroll: true } \ No newline at end of file diff --git a/src/scripts/BackTop.ts b/src/scripts/BackTop.ts index e2f9b81..65e8883 100644 --- a/src/scripts/BackTop.ts +++ b/src/scripts/BackTop.ts @@ -15,7 +15,7 @@ const scrollChangeFn = () => { const backTopFn = () => { (window as any).vhlenis && (window as any).vhlenis.stop(); window.scrollTo({ top: 0, behavior: "smooth" }); - (window as any).vhlenis.start(); + (window as any).vhlenis && (window as any).vhlenis.start(); }; // 页面更新,初始化函数====== // 回顶部DOM diff --git a/src/scripts/Init.ts b/src/scripts/Init.ts index 8e66b92..3bc16cf 100644 --- a/src/scripts/Init.ts +++ b/src/scripts/Init.ts @@ -1,6 +1,4 @@ import { inRouter, outRouter } from "../utils/updateRouter"; -// 鼠标滚动阻尼效果 -import LenisInit from './Lenis'; // 初始化文章代码块 import codeInit from "../scripts/Code"; // 初始化视频播放器 @@ -31,7 +29,7 @@ import initMobileSidebar from "../scripts/MobileSidebar"; import GoogleAdInit from "../scripts/GoogleAd"; // Han Analytics 统计 import SITE_INFO from "../config"; -const { HanAnalytics } = SITE_INFO; +const { HanAnalytics, LenisScroll } = SITE_INFO; import { LoadScript } from "../utils/index"; // ============================================================ @@ -41,7 +39,7 @@ const videoList: any[] = []; const MusicList: any[] = []; const indexInit = async (only: boolean = true) => { // 鼠标滚动阻尼效果 - only && LenisInit(); + LenisScroll && only && (await import('./Lenis')).default(); // 预加载搜索数据 only && searchFn(""); // 初始化搜索功能 diff --git a/src/scripts/Lenis.ts b/src/scripts/Lenis.ts index c0bbc21..e01c556 100644 --- a/src/scripts/Lenis.ts +++ b/src/scripts/Lenis.ts @@ -16,7 +16,7 @@ let clearTimer: any = null; (window as any).vhlenis.stop(); clearTimer = setTimeout(() => { (window as any).vhlenis.start(); - }, 566); + }, 288); } } catch { scrollStatus = null; From fcecb08c6b929e8aeabefe088f095bd112c06f11 Mon Sep 17 00:00:00 2001 From: Han <1655466387@qq.com> Date: Sun, 9 Mar 2025 12:27:02 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E2=9C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../blog/Astro主题优雅的vhAstro-Theme.md | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/src/content/blog/Astro主题优雅的vhAstro-Theme.md b/src/content/blog/Astro主题优雅的vhAstro-Theme.md index d713c79..5099530 100644 --- a/src/content/blog/Astro主题优雅的vhAstro-Theme.md +++ b/src/content/blog/Astro主题优雅的vhAstro-Theme.md @@ -5,16 +5,20 @@ tags: - Astro - 主题 - 博客 + - vhAstro - vhAstro-Theme id: "astro-theme-vhastro-theme" date: 2025-03-02 18:18:18 cover: "https://i0.wp.com/uxiaohan.github.io/v2/2025/03/1740977096666.webp" recommend: true +top: true --- :::note{type="success"} 🥝 从 Z-Blog 到 Emlog,从 Typecho 到 Hexo,从动态博客到静态博客,作为一个前端,我深入了解了多种 SSG 工具,如 Hexo、Vitepress、Hugo 等,并最终锁定了 Astro 作为重构博客的选择。 + 🍇 Astro 活跃的社区支持、广泛的现代框架兼容性、高效的性能优化、优秀的开发体验以及原生 SEO 优化,支持 Markdown/MDX 编写内容,且允许开发者混合使用 React、Vue、Svelte 等主流框架的组件,是我心目中最适合构建博客这样的以内容驱动的网站的 Web 框架。 + 🍊 我以 Astro 为基础开发的 vhAstro-Theme 主题模版,是一款优雅的响应式博客主题,它具有简洁的设计、流畅的动画和页面过渡。 ::: @@ -26,7 +30,7 @@ recommend: true ## 项目地址 -> 1、QQ 群下载:113445803
2、GitHub 下载(推荐):https://github.com/uxiaohan/vhAstro-Theme (帮忙点个star) +> 1、QQ 群下载:113445803
2、GitHub 下载(推荐):https://github.com/uxiaohan/vhAstro-Theme (谢谢你的star) ## ✨ 功能特性 @@ -59,7 +63,7 @@ recommend: true ## 🚀 使用方法 -- 使用此模板生成新仓库或 Fork 此仓库 +- 使用此模板 [生成新仓库或 Fork 此仓库](https://github.com/new?template_name=vhAstro-Theme&template_owner=uxiaohan) - 进行本地开发,Clone 新的仓库,执行 `pnpm install` 以安装依赖 - 若未安装 pnpm,执行 `npm install -g pnpm` - 通过配置文件 `src/config.ts` 自定义博客 @@ -67,6 +71,7 @@ recommend: true - 参考官方指南将博客部署至 Vercel, Netlify,Cloudflare Pages, GitHub Pages 等 - 部署前需编辑 `astro.config.mjs` 中的站点设置。 + ## ⚙️ 文章格式 ```md @@ -81,6 +86,7 @@ date: 文章创建日期 updated: 文章更新日期 cover: "封面图URL (为空默认随机内置封面 /public/assets/images/banner)" recommend: false # 是否推荐文章 +top: false # 是否置顶文章 hide: false # 是否隐藏文章 --- ``` @@ -149,6 +155,40 @@ export default { }; ``` +### 圈子(需部署FreshRSS) + +```js +// 配置文件 src/page_data/Friends.ts +export default { + // API 接口请求优先,数据格式保持和 data 一致 + api: '', + // api 为空则使用 data 静态数据 + data: [ + { + "title": "Astro 中使用 Lenis 增加鼠标滚动阻尼感", + "auther": "韩小韩博客", + "date": "2025-03-06", + "link": "https://www.vvhan.com/article/Lenis-in-Astro", + "content": "在移动端触控交互中,惯性滚动带来的丝滑体验已成为标配,但鼠标滚轮受限于机械结构,滚动时难免产生生硬的段落感。如何让传统滚轮操作也能获得如触控板般的阻尼反馈?Lenis库通过JavaScript模拟惯性算法,成功将”物理惯性”引入网页滚动,本文将解析其实现原理与实战应用。" + }, + { + "title": "Astro 添加 Twikoo 评论组件", + "auther": "韩小韩博客", + "date": "2025-03-03", + "link": "https://www.vvhan.com/article/astro-twikoo", + "content": "Astro在使用视图过渡路由时,在跳转路由时,会导致JS文件只有在第一次进入页面时生效,所以Astro在使用视图过渡路由下Twikoo时无法正常使用的,我是单独写了一个评论组件,对Twikoo进行动态加载,然后在需要评论的页面引入的。" + }, + { + "title": "Astro主题-优雅的vhAstro-Theme【使用文档】", + "auther": "韩小韩博客", + "date": "2025-03-02", + "link": "https://www.vvhan.com/article/astro-theme-vhastro-theme", + "content": "🥝从Z-Blog到Emlog,从Typecho到Hexo,从动态博客到静态博客,作为一个前端,我深入了解了多种SSG工具,如Hexo、Vitepress、Hugo等,并最终锁定了Astro作为重构博客的选择。🍇Astro活跃的社区支持、广泛的现代框架兼容性、高效的性能优化、优秀的开发体验。" + } + ] +} +``` + ## 🌈 组件 ### 文本加粗