正式版1.0✅
This commit is contained in:
parent
60f5296dcb
commit
e7117f0903
148
README.md
148
README.md
@ -1,82 +1,80 @@
|
|||||||
# vhAstro-Theme
|
# 🍥 Astro主题 vhAstro-Theme
|
||||||
一款简约的 Astro 主题
|
|
||||||
|
|
||||||
# Astro Starter Kit: Blog
|
## 🚀 vhAstro-Theme:一款基于 Astro 构建的优雅的响应式博客主题
|
||||||
|
|
||||||
```sh
|
**「当极简主义遇上工程之美」**
|
||||||
npm i pnpm -g
|
|
||||||
|
在线演示 ➡️ [https://www.vvhan.com](https://www.vvhan.com)
|
||||||
|
官方文档 ➡️ [vhAstro-Theme](https://www.vvhan.com/article/astro-theme-vhastro-theme)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## ✨ 功能特性
|
||||||
|
|
||||||
|
- [x] 简洁的响应式设计
|
||||||
|
- [x] 流畅的动画和页面过渡
|
||||||
|
- [x] 两列布局
|
||||||
|
- [x] 阅读时间
|
||||||
|
- [x] 字数统计
|
||||||
|
- [x] 代码块
|
||||||
|
- [x] 语法高亮
|
||||||
|
- [x] 图片懒加载
|
||||||
|
- [x] 图片灯箱
|
||||||
|
- [x] Twikoo 评论
|
||||||
|
- [x] 本地搜索
|
||||||
|
- [x] 标签
|
||||||
|
- [x] 分类
|
||||||
|
- [x] 归档
|
||||||
|
- [x] 动态
|
||||||
|
- [x] 关于
|
||||||
|
- [x] 友情链接
|
||||||
|
- [x] 推荐文章
|
||||||
|
- [x] 谷歌广告
|
||||||
|
- [x] 内置 404 页面
|
||||||
|
- [x] Sitemap 支持
|
||||||
|
- [x] RSS 支持
|
||||||
|
- [x] 活跃的社区支持
|
||||||
|
- [x] 广泛的现代框架兼容性
|
||||||
|
- [x] 高效的性能优化
|
||||||
|
- [x] 优秀的开发体验
|
||||||
|
|
||||||
|
## 🚀 使用方法
|
||||||
|
|
||||||
|
- 使用此模板生成新仓库或 Fork 此仓库
|
||||||
|
- 进行本地开发,Clone 新的仓库,执行 `pnpm install` 以安装依赖
|
||||||
|
- 若未安装 pnpm,执行 `npm install -g pnpm`
|
||||||
|
- 通过配置文件 `src/config.ts` 自定义博客
|
||||||
|
- 执行 pnpm newpost '文章标题' 创建新文章,并在 src/content/posts/ 目录中编辑
|
||||||
|
- 参考官方指南将博客部署至 Vercel, Netlify,Cloudflare Pages, GitHub Pages 等
|
||||||
|
- 部署前需编辑 `astro.config.mjs` 中的站点设置。
|
||||||
|
|
||||||
|
## ⚙️ 文章格式
|
||||||
|
|
||||||
|
```md
|
||||||
|
---
|
||||||
|
title: 标题
|
||||||
|
categories: 分类
|
||||||
|
tags:
|
||||||
|
- 标签1
|
||||||
|
- 标签2
|
||||||
|
id: 文章ID
|
||||||
|
date: 文章创建日期
|
||||||
|
updated: 文章更新日期
|
||||||
|
cover: "封面图URL (为空默认随机内置封面 /public/assets/images/banner)"
|
||||||
|
recommend: false # 是否推荐文章
|
||||||
|
hide: false # 是否隐藏文章
|
||||||
|
---
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh
|
## 💻 命令
|
||||||
|
|
||||||
|
```bash
|
||||||
# 安装依赖
|
# 安装依赖
|
||||||
pnpm i
|
pnpm install
|
||||||
```
|
# 本地开发
|
||||||
|
|
||||||
```sh
|
|
||||||
# 启动开发环境
|
|
||||||
pnpm dev
|
pnpm dev
|
||||||
```
|
# 构建静态文件
|
||||||
|
|
||||||
```sh
|
|
||||||
# 编译
|
|
||||||
pnpm build
|
pnpm build
|
||||||
```
|
# 创建新文章
|
||||||
|
pnpm newpost '文章标题'
|
||||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
```
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Features:
|
|
||||||
|
|
||||||
- ✅ Minimal styling (make it your own!)
|
|
||||||
- ✅ 100/100 Lighthouse performance
|
|
||||||
- ✅ SEO-friendly with canonical URLs and OpenGraph data
|
|
||||||
- ✅ Sitemap support
|
|
||||||
- ✅ RSS Feed support
|
|
||||||
- ✅ Markdown & MDX support
|
|
||||||
|
|
||||||
## 🚀 Project Structure
|
|
||||||
|
|
||||||
Inside of your Astro project, you'll see the following folders and files:
|
|
||||||
|
|
||||||
```text
|
|
||||||
├── public/
|
|
||||||
├── src/
|
|
||||||
│ ├── components/
|
|
||||||
│ ├── content/
|
|
||||||
│ ├── layouts/
|
|
||||||
│ └── pages/
|
|
||||||
├── astro.config.mjs
|
|
||||||
├── README.md
|
|
||||||
├── package.json
|
|
||||||
└── tsconfig.json
|
|
||||||
```
|
|
||||||
|
|
||||||
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
|
||||||
|
|
||||||
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
|
|
||||||
|
|
||||||
The `src/content/` directory contains "collections" of related Markdown and MDX documents. Use `getCollection()` to retrieve posts from `src/content/blog/`, and type-check your frontmatter using an optional schema. See [Astro's Content Collections docs](https://docs.astro.build/en/guides/content-collections/) to learn more.
|
|
||||||
|
|
||||||
Any static assets, like images, can be placed in the `public/` directory.
|
|
||||||
|
|
||||||
## 🧞 Commands
|
|
||||||
|
|
||||||
All commands are run from the root of the project, from a terminal:
|
|
||||||
|
|
||||||
| Command | Action |
|
|
||||||
| :------------------------ | :----------------------------------------------- |
|
|
||||||
| `npm install` | Installs dependencies |
|
|
||||||
| `npm run dev` | Starts local dev server at `localhost:4321` |
|
|
||||||
| `npm run build` | Build your production site to `./dist/` |
|
|
||||||
| `npm run preview` | Preview your build locally, before deploying |
|
|
||||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
|
||||||
| `npm run astro -- --help` | Get help using the Astro CLI |
|
|
||||||
|
|
||||||
## 👀 Want to learn more?
|
|
||||||
|
|
||||||
Check out [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
|
||||||
|
|
||||||
## Credit
|
|
||||||
|
|
||||||
This theme is based off of the lovely [Bear Blog](https://github.com/HermanMartinus/bearblog/).
|
|
||||||
@ -4,17 +4,32 @@ import sitemap from '@astrojs/sitemap';
|
|||||||
import { defineConfig } from 'astro/config';
|
import { defineConfig } from 'astro/config';
|
||||||
import remarkDirective from "remark-directive"; /* Handle directives */
|
import remarkDirective from "remark-directive"; /* Handle directives */
|
||||||
import { remarkNote, addClassNames } from './src/plugins/markdown.formate'
|
import { remarkNote, addClassNames } from './src/plugins/markdown.formate'
|
||||||
|
import swup from '@swup/astro';
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
site: 'https://www.vvhan.com',
|
site: 'https://www.vvhan.com',
|
||||||
integrations: [mdx(), sitemap({
|
integrations: [
|
||||||
changefreq: 'weekly',
|
swup({
|
||||||
priority: 0.7,
|
theme: false,
|
||||||
lastmod: new Date(),
|
animationClass: "vh-animation-",
|
||||||
// 处理末尾带 / 的 url
|
containers: [".vh-animation"],
|
||||||
serialize: (item) => ({ ...item, url: item.url.endsWith('/') ? item.url.slice(0, -1) : item.url })
|
smoothScrolling: true,
|
||||||
})],
|
progress: true,
|
||||||
|
cache: true,
|
||||||
|
preload: true,
|
||||||
|
accessibility: true,
|
||||||
|
updateHead: true,
|
||||||
|
updateBodyClass: false,
|
||||||
|
globalInstance: true
|
||||||
|
}),
|
||||||
|
sitemap({
|
||||||
|
changefreq: 'weekly',
|
||||||
|
priority: 0.7,
|
||||||
|
lastmod: new Date(),
|
||||||
|
// 处理末尾带 / 的 url
|
||||||
|
serialize: (item) => ({ ...item, url: item.url.endsWith('/') ? item.url.slice(0, -1) : item.url })
|
||||||
|
}),
|
||||||
|
mdx()],
|
||||||
markdown: {
|
markdown: {
|
||||||
rehypePlugins: [addClassNames],
|
rehypePlugins: [addClassNames],
|
||||||
remarkPlugins: [remarkDirective, remarkNote],
|
remarkPlugins: [remarkDirective, remarkNote],
|
||||||
|
|||||||
15
package.json
15
package.json
@ -10,16 +10,18 @@
|
|||||||
"newpost": "node ./script/newpost.js"
|
"newpost": "node ./script/newpost.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/mdx": "^4.0.8",
|
"@astrojs/mdx": "^4.1.0",
|
||||||
"@astrojs/rss": "^4.0.11",
|
"@astrojs/rss": "^4.0.11",
|
||||||
"@astrojs/sitemap": "^3.2.1",
|
"@astrojs/sitemap": "^3.2.1",
|
||||||
"astro": "^5.3.0",
|
"@swup/astro": "^1.5.0",
|
||||||
|
"astro": "^5.4.1",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"dplayer": "^1.27.1",
|
"dplayer": "^1.27.1",
|
||||||
"hls.js": "^1.5.20",
|
"hls.js": "^1.5.20",
|
||||||
"nprogress": "^0.2.0",
|
"mdast-util-to-string": "^4.0.0",
|
||||||
"overlayscrollbars": "^2.11.0",
|
"overlayscrollbars": "^2.11.1",
|
||||||
"remark-directive": "^3.0.1",
|
"reading-time": "^1.5.0",
|
||||||
|
"remark-directive": "^4.0.0",
|
||||||
"twikoo": "1.6.41",
|
"twikoo": "1.6.41",
|
||||||
"unist-util-visit": "^5.0.0",
|
"unist-util-visit": "^5.0.0",
|
||||||
"vanilla-lazyload": "^19.1.3",
|
"vanilla-lazyload": "^19.1.3",
|
||||||
@ -34,7 +36,8 @@
|
|||||||
"pnpm": {
|
"pnpm": {
|
||||||
"onlyBuiltDependencies": [
|
"onlyBuiltDependencies": [
|
||||||
"esbuild",
|
"esbuild",
|
||||||
"sharp"
|
"sharp",
|
||||||
|
"swup"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
@ -8,13 +7,13 @@ const __filename = fileURLToPath(import.meta.url);
|
|||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
// 获取命令行参数
|
// 获取命令行参数
|
||||||
const articleName = process.argv.slice(2).join('');
|
const articleName = process.argv.slice(2).join('');
|
||||||
const articleID = crypto.createHash('md5').update(String(dayjs().valueOf())).digest('hex');
|
const articleID = crypto.createHash('sha256').update(dayjs().valueOf().toString()).digest('hex').slice(0, 16);
|
||||||
if (!articleName) {
|
if (!articleName) {
|
||||||
console.error('请提供文章名称,例如:pnpm newpost "第一篇文章"');
|
console.error('请提供文章名称,例如:pnpm newpost "第一篇文章"');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
const ArticleContent = `---
|
const ArticleContent = `---
|
||||||
title: ${articleName}
|
title: "${articleName.replace(/"/g, '\\"')}"
|
||||||
categories: 分类
|
categories: 分类
|
||||||
tags:
|
tags:
|
||||||
- 标签
|
- 标签
|
||||||
@ -34,8 +33,22 @@ const init = async () => {
|
|||||||
// 写文件
|
// 写文件
|
||||||
const now = dayjs();
|
const now = dayjs();
|
||||||
const targetDir = path.join(__dirname, '../src/content/blog', `${now.year()}/${now.format('MM')}`);
|
const targetDir = path.join(__dirname, '../src/content/blog', `${now.year()}/${now.format('MM')}`);
|
||||||
await fs.mkdir(path.dirname(targetDir), { recursive: true });
|
try {
|
||||||
await fs.writeFile(path.join(targetDir, `${articleName}.md`), ArticleContent, 'utf8');
|
await fs.mkdir(targetDir, { recursive: true });
|
||||||
console.log(`文章 ${articleName} 已创建`);
|
await fs.writeFile(path.join(targetDir, `${articleName}.md`), ArticleContent, 'utf8');
|
||||||
|
const filePath = path.join(targetDir, `${articleName}.md`);
|
||||||
|
await fs.writeFile(path.join(targetDir, `${articleName}.md`), ArticleContent, 'utf8');
|
||||||
|
// 友好输出
|
||||||
|
console.log('✅ 文章创建成功');
|
||||||
|
console.log(`📅 日期:${now.format('YYYY-MM-DD')}`);
|
||||||
|
console.log(`📂 路径:${filePath}`);
|
||||||
|
console.log(`🆔 ID:${articleID.slice(0, 16)} (可手动修改)`);
|
||||||
|
} catch (error) {
|
||||||
|
// 增强错误处理
|
||||||
|
console.error('❌ 创建失败:');
|
||||||
|
console.error(`错误类型:${error.code || 'UNKNOWN_ERROR'}`);
|
||||||
|
console.error(`详细信息:${error.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
init();
|
init();
|
||||||
@ -5,7 +5,7 @@ const { articleList } = Astro.props;
|
|||||||
import "../styles/Archive.less";
|
import "../styles/Archive.less";
|
||||||
---
|
---
|
||||||
|
|
||||||
<section class="vh-animation vh-archive-main">
|
<section class="vh-archive-main vh-animation vh-animation-init">
|
||||||
<div class="archive-list">
|
<div class="archive-list">
|
||||||
{
|
{
|
||||||
articleList.map((i: any) => (
|
articleList.map((i: any) => (
|
||||||
@ -13,7 +13,7 @@ import "../styles/Archive.less";
|
|||||||
<p class="title">
|
<p class="title">
|
||||||
<em>{i.name}</em>
|
<em>{i.name}</em>
|
||||||
<i />
|
<i />
|
||||||
<span>{i.data.length} total</span>
|
<span>{i.data.length}篇文章</span>
|
||||||
</p>
|
</p>
|
||||||
{i.data.map((_: any) => (
|
{i.data.map((_: any) => (
|
||||||
<a href={`/article/${_.id}`}>
|
<a href={`/article/${_.id}`}>
|
||||||
|
|||||||
@ -11,7 +11,7 @@ const ARTICLE_COVER: string = await getCover(post.data.cover);
|
|||||||
import "../styles/components/ArticleCard.less";
|
import "../styles/components/ArticleCard.less";
|
||||||
---
|
---
|
||||||
|
|
||||||
<article class="vh-article-item vh-animation" style={`animation-delay:calc(var(--vh-animation-delay) + ${index * 50}ms)`}>
|
<article class="vh-article-item vh-animation vh-animation-init">
|
||||||
<a class="vh-article-link" href={`/article/${post.data.id}`}>
|
<a class="vh-article-link" href={`/article/${post.data.id}`}>
|
||||||
<section class="vh-article-banner"><Image src="/assets/images/lazy-loading.webp" data-vh-lz-src={ARTICLE_COVER} alt={post.data.title} width="10" height="10" /></section>
|
<section class="vh-article-banner"><Image src="/assets/images/lazy-loading.webp" data-vh-lz-src={ARTICLE_COVER} alt={post.data.title} width="10" height="10" /></section>
|
||||||
<section class="vh-article-desc">
|
<section class="vh-article-desc">
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
---
|
---
|
||||||
|
// 静态图片
|
||||||
|
import { Image } from "astro:assets";
|
||||||
|
// 时间处理
|
||||||
|
import { fmtTime } from "../utils/index";
|
||||||
// 获取用户配置数据
|
// 获取用户配置数据
|
||||||
import SITE_CONFIG from "../config";
|
import SITE_CONFIG from "../config";
|
||||||
import { fmtTime } from "../utils/index";
|
const { Avatar, Author, Motto, WebSites, GoogleAds } = SITE_CONFIG;
|
||||||
const { Avatar, Author, Motto, WebSites } = SITE_CONFIG;
|
|
||||||
// 获取文章数据
|
// 获取文章数据
|
||||||
import { getCategories, getTags, getRecommendArticles } from "../utils/getPostInfo";
|
import { getCategories, getTags, getRecommendArticles } from "../utils/getPostInfo";
|
||||||
// 分类列表
|
// 分类列表
|
||||||
@ -11,8 +14,8 @@ const categories = getCategories();
|
|||||||
const tags = getTags();
|
const tags = getTags();
|
||||||
// 最新文章
|
// 最新文章
|
||||||
const recommendArticles = getRecommendArticles();
|
const recommendArticles = getRecommendArticles();
|
||||||
// 静态图片
|
// Google 广告组件
|
||||||
import { Image } from "astro:assets";
|
import GoogleAd from "../components/GoogleAd.astro";
|
||||||
// 侧边栏样式
|
// 侧边栏样式
|
||||||
import "../styles/components/Aside.less";
|
import "../styles/components/Aside.less";
|
||||||
---
|
---
|
||||||
@ -20,10 +23,7 @@ import "../styles/components/Aside.less";
|
|||||||
<aside class="vh-aside">
|
<aside class="vh-aside">
|
||||||
<!-- 头像块 -->
|
<!-- 头像块 -->
|
||||||
<section class="vh-aside-item user">
|
<section class="vh-aside-item user">
|
||||||
<div onclick="window.history.back()" class="vh-aside-avatar">
|
<Image class="vh-aside-avatar" src="/assets/images/lazy-loading.webp" data-vh-lz-src={Avatar} alt={Author} width="1" height="1" />
|
||||||
<Image class="vh-aside-avatar" src={Avatar} alt={Author} width="1" height="1" />
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <path d="M512 256A256 256 0 1 0 0 256a256 256 0 1 0 512 0zM116.7 244.7l112-112c4.6-4.6 11.5-5.9 17.4-3.5s9.9 8.3 9.9 14.8l0 64 96 0c17.7 0 32 14.3 32 32l0 32c0 17.7-14.3 32-32 32l-96 0 0 64c0 6.5-3.9 12.3-9.9 14.8s-12.9 1.1-17.4-3.5l-112-112c-6.2-6.2-6.2-16.4 0-22.6z"></path></svg>
|
|
||||||
</div>
|
|
||||||
<span class="vh-aside-auther">{Author}</span>
|
<span class="vh-aside-auther">{Author}</span>
|
||||||
<p class="vh-aside-motto">{Motto}</p>
|
<p class="vh-aside-motto">{Motto}</p>
|
||||||
<section class="vh-aside-links">
|
<section class="vh-aside-links">
|
||||||
@ -60,7 +60,7 @@ import "../styles/components/Aside.less";
|
|||||||
<div class="vh-aside-tags">
|
<div class="vh-aside-tags">
|
||||||
{
|
{
|
||||||
tags.map(i => (
|
tags.map(i => (
|
||||||
<a href={`/tags/${i}`}>
|
<a href={`/tag/${i}`}>
|
||||||
<span>{i}</span>
|
<span>{i}</span>
|
||||||
</a>
|
</a>
|
||||||
))
|
))
|
||||||
@ -68,23 +68,37 @@ import "../styles/components/Aside.less";
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- 最新文章块 -->
|
<section class="sticky-aside">
|
||||||
<section class="vh-aside-item articles">
|
<!-- 最新文章块 -->
|
||||||
<h3>推荐文章</h3>
|
{
|
||||||
<div class="vh-aside-articles">
|
recommendArticles.length && (
|
||||||
{
|
<section class="vh-aside-item articles">
|
||||||
recommendArticles.map(async i => (
|
<h3>推荐文章</h3>
|
||||||
<a href={`/article/${(await i).id}`}>
|
<div class="vh-aside-articles">
|
||||||
<p class="cover">
|
{recommendArticles.map(async i => (
|
||||||
<Image src={(await i).cover} alt={(await i).title} width="1" height="1" />
|
<a href={`/article/${(await i).id}`}>
|
||||||
</p>
|
<p class="cover">
|
||||||
<p class="info">
|
<Image src="/assets/images/lazy-loading.webp" data-vh-lz-src={(await i).cover} alt={(await i).title} width="1" height="1" />
|
||||||
<span>{(await i).title}</span>
|
</p>
|
||||||
<time>{fmtTime((await i).date, "YYYY-MM-DD A")}</time>
|
<p class="info">
|
||||||
</p>
|
<span>{(await i).title}</span>
|
||||||
</a>
|
<time>{fmtTime((await i).date, "YYYY-MM-DD A")}</time>
|
||||||
))
|
</p>
|
||||||
}
|
</a>
|
||||||
</div>
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- 谷歌广告块 -->
|
||||||
|
{
|
||||||
|
GoogleAds.ad_Client && GoogleAds.asideAD_Slot && (
|
||||||
|
<section class="vh-aside-item ad">
|
||||||
|
<h3>广而告之</h3>
|
||||||
|
<GoogleAd className="vh-aside-ad" slotID={GoogleAds.asideAD_Slot} />
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
</section>
|
</section>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
@ -14,10 +14,4 @@ import "../styles/components/BackTop.less";
|
|||||||
<circle stroke="url(#Gradient)" cx="12" cy="12" r="10" fill="none" stroke-width="2" stroke-linecap="round" transform="rotate(-90, 12, 12)"></circle>
|
<circle stroke="url(#Gradient)" cx="12" cy="12" r="10" fill="none" stroke-width="2" stroke-linecap="round" transform="rotate(-90, 12, 12)"></circle>
|
||||||
</svg>
|
</svg>
|
||||||
<svg class="icon" viewBox="0 0 24 24"><path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m6 15l6-6l6 6"></path></svg>
|
<svg class="icon" viewBox="0 0 24 24"><path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m6 15l6-6l6 6"></path></svg>
|
||||||
</section>
|
</section>
|
||||||
<script>
|
|
||||||
import updateRouter from "../utils/updateRouter";
|
|
||||||
import BackTopInitFn from "../scripts/BackTop";
|
|
||||||
// 初始化BackTop组件
|
|
||||||
updateRouter("afterMount", BackTopInitFn);
|
|
||||||
</script>
|
|
||||||
@ -3,16 +3,3 @@ import "../styles/components/Comment.less";
|
|||||||
---
|
---
|
||||||
|
|
||||||
<section class="vh-comment"><section></section></section>
|
<section class="vh-comment"><section></section></section>
|
||||||
<script>
|
|
||||||
import updateRouter from "../utils/updateRouter";
|
|
||||||
import { LoadScript } from "../utils/index";
|
|
||||||
import SITE_INFO from "../config";
|
|
||||||
declare const twikoo: any;
|
|
||||||
// 初始化评论插件
|
|
||||||
SITE_INFO.Twikoo.envId &&
|
|
||||||
updateRouter("afterMount", async () => {
|
|
||||||
if (!document.querySelector(".vh-comment>section")) return;
|
|
||||||
await LoadScript("https://registry.npmmirror.com/twikoo/1.6.41/files/dist/twikoo.all.min.js");
|
|
||||||
twikoo.init({ envId: SITE_INFO.Twikoo.envId, el: ".vh-comment>section" });
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -11,15 +11,3 @@ import "../styles/Footer.less";
|
|||||||
<p><a href="https://astro.build/" target="_blank" rel="noopener noreferrer"><Image width="1" height="1" alt="Astro" src="/assets/images/footer/astro.svg" /></a><a href="https://github.com/uxiaohan/vhAstro-Theme" target="_blank" rel="noopener noreferrer"><Image width="1" height="1" alt="vhAstro-Theme" src="/assets/images/footer/theme.svg" /></a><a href="/sitemap-index.xml" target="_blank"><Image width="1" height="1" alt="sitemap" src="/assets/images/footer/sitemap.svg" /></a><a href="/rss.xml" target="_blank"><Image width="1" height="1" alt="rss" src="/assets/images/footer/rss.svg" /></a><a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer"><Image width="1" height="1" alt="icp" src="/assets/images/footer/icp.svg" /></a></p>
|
<p><a href="https://astro.build/" target="_blank" rel="noopener noreferrer"><Image width="1" height="1" alt="Astro" src="/assets/images/footer/astro.svg" /></a><a href="https://github.com/uxiaohan/vhAstro-Theme" target="_blank" rel="noopener noreferrer"><Image width="1" height="1" alt="vhAstro-Theme" src="/assets/images/footer/theme.svg" /></a><a href="/sitemap-index.xml" target="_blank"><Image width="1" height="1" alt="sitemap" src="/assets/images/footer/sitemap.svg" /></a><a href="/rss.xml" target="_blank"><Image width="1" height="1" alt="rss" src="/assets/images/footer/rss.svg" /></a><a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer"><Image width="1" height="1" alt="icp" src="/assets/images/footer/icp.svg" /></a></p>
|
||||||
</main>
|
</main>
|
||||||
</footer>
|
</footer>
|
||||||
<script>
|
|
||||||
import updateRouter from "../utils/updateRouter";
|
|
||||||
// 格式化时间
|
|
||||||
import { fmtDate } from "../utils/index";
|
|
||||||
// 页面内容的元数据
|
|
||||||
import SITE_CONFIG from "../config";
|
|
||||||
const { CreateTime } = SITE_CONFIG;
|
|
||||||
// 初始化 网站运行时间
|
|
||||||
updateRouter("afterMount", () => {
|
|
||||||
document.querySelector("em.web_time")!.textContent = fmtDate(CreateTime);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
5
src/components/GoogleAd.astro
Normal file
5
src/components/GoogleAd.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
const { slotID, className } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class={`vh-ad ${className}`} set:html={slotID} />
|
||||||
@ -1,6 +1,4 @@
|
|||||||
---
|
---
|
||||||
// 导航高亮
|
|
||||||
const { activeNav } = Astro.props;
|
|
||||||
import SITE_CONFIG from "../config";
|
import SITE_CONFIG from "../config";
|
||||||
const { Navs } = SITE_CONFIG;
|
const { Navs } = SITE_CONFIG;
|
||||||
// 原生图片
|
// 原生图片
|
||||||
@ -17,10 +15,10 @@ import "../styles/components/Header.less";
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"> <path d="M543.8 287.6c17 0 32-14 32-32.1c1-9-3-17-11-24L309.5 7c-6-5-14-7-21-7s-15 1-22 8L10 231.5c-7 7-10 15-10 24c0 18 14 32.1 32 32.1l32 0 0 160.4c0 35.3 28.7 64 64 64l102.3 0-31.3-52.2c-4.1-6.8-2.6-15.5 3.5-20.5L288 368l-60.2-82.8c-10.9-15 8.2-33.5 22.8-22l117.9 92.6c8 6.3 8.2 18.4 .4 24.9L288 448l38.4 64 122.1 0c35.5 0 64.2-28.8 64-64.3l-.7-160.2 32 0z"></path></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"> <path d="M543.8 287.6c17 0 32-14 32-32.1c1-9-3-17-11-24L309.5 7c-6-5-14-7-21-7s-15 1-22 8L10 231.5c-7 7-10 15-10 24c0 18 14 32.1 32 32.1l32 0 0 160.4c0 35.3 28.7 64 64 64l102.3 0-31.3-52.2c-4.1-6.8-2.6-15.5 3.5-20.5L288 368l-60.2-82.8c-10.9-15 8.2-33.5 22.8-22l117.9 92.6c8 6.3 8.2 18.4 .4 24.9L288 448l38.4 64 122.1 0c35.5 0 64.2-28.8 64-64.3l-.7-160.2 32 0z"></path></svg>
|
||||||
Home
|
Home
|
||||||
</a>
|
</a>
|
||||||
<div class="link-list">
|
<div class="link-list vh-link-list">
|
||||||
{
|
{
|
||||||
Navs.map(i => (
|
Navs.map(i => (
|
||||||
<a class={i.link.includes(activeNav) ? "active" : ""} href={i.link} target={i.target ? "_blank" : "_self"}>
|
<a class={i.link.replace("/", "")} href={i.link} target={i.target ? "_blank" : "_self"}>
|
||||||
{i.text}
|
{i.text}
|
||||||
<Image src={i.icon} alt={i.text} width="10" height="10" />
|
<Image src={i.icon} alt={i.text} width="10" height="10" />
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
---
|
---
|
||||||
// 导航高亮
|
|
||||||
const { activeNav } = Astro.props;
|
|
||||||
import SITE_CONFIG from "../config";
|
import SITE_CONFIG from "../config";
|
||||||
const { Navs, Title } = SITE_CONFIG;
|
const { Navs, Title } = SITE_CONFIG;
|
||||||
// 侧边栏 MobileSidebar 样式
|
// 侧边栏 MobileSidebar 样式
|
||||||
@ -13,10 +11,10 @@ import "../styles/components/MobileSidebar.less";
|
|||||||
<h3>{Title}</h3>
|
<h3>{Title}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="vh-mobilesidebar-list">
|
<div class="vh-mobilesidebar-list vh-link-list">
|
||||||
{
|
{
|
||||||
Navs.map(i => (
|
Navs.map(i => (
|
||||||
<a class={i.link.includes(activeNav) ? "active" : ""} href={i.link} target={i.target ? "_blank" : "_self"}>
|
<a class={i.link.replace("/", "")} href={i.link} target={i.target ? "_blank" : "_self"}>
|
||||||
<object data={i.icon} type="image/svg+xml" />
|
<object data={i.icon} type="image/svg+xml" />
|
||||||
{i.text}
|
{i.text}
|
||||||
</a>
|
</a>
|
||||||
@ -25,18 +23,3 @@ import "../styles/components/MobileSidebar.less";
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<script>
|
|
||||||
import updateRouter from "../utils/updateRouter";
|
|
||||||
// 初始化搜索框
|
|
||||||
const vhSearchInit = () => {
|
|
||||||
const menuDOM: any = document.querySelector(".vh-header>.main>.nav-btn>span.menu-btn");
|
|
||||||
const mobileSidebarDOM: any = document.querySelector("body>.vh-mobilesidebar");
|
|
||||||
const addActive = () => setTimeout(() => mobileSidebarDOM.classList.add("active"));
|
|
||||||
const removeActive = () => setTimeout(() => mobileSidebarDOM.classList.remove("active"));
|
|
||||||
menuDOM.addEventListener("click", addActive);
|
|
||||||
mobileSidebarDOM.addEventListener("click", removeActive);
|
|
||||||
};
|
|
||||||
// 初始化
|
|
||||||
updateRouter("afterMount", vhSearchInit);
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -11,25 +11,4 @@ import "../styles/components/Search.less";
|
|||||||
</div>
|
</div>
|
||||||
<section class="vh-search-list"></section>
|
<section class="vh-search-list"></section>
|
||||||
</main>
|
</main>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script>
|
|
||||||
import updateRouter from "../utils/updateRouter";
|
|
||||||
import { searchInputChange } from "../scripts/Search";
|
|
||||||
// 初始化搜索框
|
|
||||||
const vhSearchInit = () => {
|
|
||||||
const searchDOM: any = document.querySelector(".vh-header>.main>.nav-btn>span.search-btn");
|
|
||||||
const searchMainDOM: any = document.querySelector(".vh-header>.main>.vh-search>main");
|
|
||||||
const searchListDOM: any = document.querySelector(".vh-header>.main>.vh-search");
|
|
||||||
const addActive = () => setTimeout(() => searchListDOM.classList.add("active"));
|
|
||||||
const removeActive = () => setTimeout(() => searchListDOM.classList.remove("active"));
|
|
||||||
// 禁止默认事件
|
|
||||||
searchMainDOM.addEventListener("click", (e: Event) => e.stopPropagation());
|
|
||||||
searchDOM.addEventListener("click", addActive);
|
|
||||||
searchListDOM.addEventListener("click", removeActive);
|
|
||||||
// 搜索框初内容变化
|
|
||||||
searchListDOM.querySelector(".search-input>input").addEventListener("input", searchInputChange);
|
|
||||||
};
|
|
||||||
// 初始化
|
|
||||||
updateRouter("afterMount", vhSearchInit);
|
|
||||||
</script>
|
|
||||||
@ -40,5 +40,13 @@ export default {
|
|||||||
// 评论组件 Twikoo
|
// 评论组件 Twikoo
|
||||||
Twikoo: { envId: 'https://twikoo.vvhan.com/.netlify/functions/twikoo' },
|
Twikoo: { envId: 'https://twikoo.vvhan.com/.netlify/functions/twikoo' },
|
||||||
// Han Analytics 统计(https://github.com/uxiaohan/HanAnalytics)
|
// Han Analytics 统计(https://github.com/uxiaohan/HanAnalytics)
|
||||||
HanAnalytics: { enable: true, server: 'https://analytics.vvhan.com', siteId: 'Hello-HanHexoBlog' }
|
HanAnalytics: { enable: true, server: 'https://analytics.vvhan.com', siteId: 'Hello-HanHexoBlog' },
|
||||||
|
// Google 广告
|
||||||
|
GoogleAds: {
|
||||||
|
ad_Client: 'ca-pub-3037456000720711',
|
||||||
|
// 侧边栏广告(不填不开启)
|
||||||
|
asideAD_Slot: `<ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-3037456000720711" data-ad-slot="6102098907" data-ad-format="auto" data-full-width-responsive="true"></ins>`,
|
||||||
|
// 文章页广告(不填不开启)
|
||||||
|
articleAD_Slot: `<ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-3037456000720711" data-ad-slot="8753809833" data-ad-format="auto" data-full-width-responsive="true"></ins>`
|
||||||
|
},
|
||||||
}
|
}
|
||||||
@ -13,7 +13,8 @@ const blog = defineCollection({
|
|||||||
tags: z.array(z.union([z.string(), z.number()])),
|
tags: z.array(z.union([z.string(), z.number()])),
|
||||||
id: z.union([z.string(), z.number()]),
|
id: z.union([z.string(), z.number()]),
|
||||||
cover: z.string().optional(),
|
cover: z.string().optional(),
|
||||||
recommend: z.boolean().optional()
|
recommend: z.boolean().optional(),
|
||||||
|
hide: z.boolean().optional()
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
---
|
---
|
||||||
import { ClientRouter } from "astro:transitions";
|
const { title, keywords, description, pagecover } = Astro.props;
|
||||||
const { title, keywords, description, pagecover, activeNav } = Astro.props;
|
// 网站配置
|
||||||
|
import SITE_INFO from "../config";
|
||||||
|
const { GoogleAds, Twikoo } = SITE_INFO;
|
||||||
|
const { ad_Client, asideAD_Slot, articleAD_Slot } = GoogleAds;
|
||||||
// Head 依赖
|
// Head 依赖
|
||||||
import Head from "../components/Head.astro";
|
import Head from "../components/Head.astro";
|
||||||
// 顶部 Header
|
// 顶部 Header
|
||||||
@ -16,44 +19,24 @@ import "../styles/Layout.less";
|
|||||||
---
|
---
|
||||||
|
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<Head Title={title} Keywords={keywords} Description={description} PageCover={pagecover} />
|
<Head Title={title} Keywords={keywords} Description={description} PageCover={pagecover}>
|
||||||
<ClientRouter />
|
<!-- TwikooJS 加载项 -->
|
||||||
|
{Twikoo.envId && <script is:inline async src={`https://registry.npmmirror.com/twikoo/1.6.41/files/dist/twikoo.all.min.js`} />}
|
||||||
|
<!-- 谷歌广告JS加载项 -->
|
||||||
|
{ad_Client && (asideAD_Slot || articleAD_Slot) && <script is:inline async src={`https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${ad_Client}`} crossorigin="anonymous" />}
|
||||||
|
</Head>
|
||||||
<body>
|
<body>
|
||||||
<MobileSidebar activeNav={activeNav} />
|
<MobileSidebar />
|
||||||
<Header activeNav={activeNav} />
|
<Header />
|
||||||
<main class="vh-main">
|
<main class="vh-main">
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
<BackTop />
|
<BackTop />
|
||||||
<script>
|
<script>
|
||||||
import updateRouter from "../utils/updateRouter";
|
import InitFn from "../scripts/Init";
|
||||||
// 搜索
|
// 全局初始化
|
||||||
import { searchFn } from "../scripts/Search";
|
InitFn();
|
||||||
// 图片懒加载
|
|
||||||
import vhLzImgInit from "../scripts/vhLazyImg";
|
|
||||||
// 加载进度条
|
|
||||||
import NProgress from "nprogress";
|
|
||||||
import "nprogress/nprogress.css";
|
|
||||||
NProgress.configure({ easing: "ease", speed: 500, showSpinner: false, trickleSpeed: 200, minimum: 0.3 });
|
|
||||||
// Han Analytics 统计
|
|
||||||
import SITE_INFO from "../config";
|
|
||||||
const { HanAnalytics } = SITE_INFO;
|
|
||||||
import { LoadScript } from "../utils/index";
|
|
||||||
// 初始化 图片懒加载初
|
|
||||||
updateRouter("afterMount", () => {
|
|
||||||
// 预加载搜索数据
|
|
||||||
searchFn("");
|
|
||||||
// 图片懒加载初始化
|
|
||||||
vhLzImgInit();
|
|
||||||
// 进入页面,进度条结束
|
|
||||||
NProgress.done();
|
|
||||||
// Han Analytics 统计
|
|
||||||
HanAnalytics.enable && LoadScript(`${HanAnalytics.server}/tracker.min.js`, [{ k: "data-website-id", v: HanAnalytics.siteId }]);
|
|
||||||
});
|
|
||||||
// 离开页面,进度条开始
|
|
||||||
updateRouter("beforeCreate", NProgress.start);
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import "../styles/Article.less";
|
|||||||
|
|
||||||
<Layout title="404 Not Found" keywords={[404]} description="404 Not Found">
|
<Layout title="404 Not Found" keywords={[404]} description="404 Not Found">
|
||||||
<section class="vh-container">
|
<section class="vh-container">
|
||||||
<article class="vh-animation vh-article-main">
|
<article class="vh-article-main vh-animation vh-animation-init">
|
||||||
<header>
|
<header>
|
||||||
<h1 class="error-title">404 Not Found</h1>
|
<h1 class="error-title">404 Not Found</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@ -8,7 +8,11 @@ export async function getStaticPaths(options: GetStaticPathsOptions) {
|
|||||||
// 生成 Search JSON 文件 ======
|
// 生成 Search JSON 文件 ======
|
||||||
await setSearchJson(posts);
|
await setSearchJson(posts);
|
||||||
// 生成 Search JSON 文件 ======
|
// 生成 Search JSON 文件 ======
|
||||||
return paginate(posts, { pageSize: 15 });
|
// 隐藏的文章不显示
|
||||||
|
return paginate(
|
||||||
|
posts.filter(i => !i.data.hide),
|
||||||
|
{ pageSize: 15 }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { page } = Astro.props;
|
const { page } = Astro.props;
|
||||||
@ -27,9 +31,9 @@ import Pagination from "../components/Pagination.astro";
|
|||||||
const currentPage = page_data.url.current.replace("/", "");
|
const currentPage = page_data.url.current.replace("/", "");
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={currentPage ? `第${currentPage}页` : ""} description={Description}>
|
<Layout title={currentPage ? `第${currentPage}页文章` : ""} description={Description}>
|
||||||
<section class="vh-container">
|
<section class="vh-container">
|
||||||
<section class="vh-animation article-list">
|
<section class="article-list vh-animation vh-animation-init">
|
||||||
<!-- 文章列表 -->
|
<!-- 文章列表 -->
|
||||||
{data.map((post, index) => <ArticleCard post={post} index={index} />)}
|
{data.map((post, index) => <ArticleCard post={post} index={index} />)}
|
||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
|
|||||||
@ -14,9 +14,9 @@ import Comment from "../../components/Comment.astro";
|
|||||||
import "../../styles/About.less";
|
import "../../styles/About.less";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title="关于" description={Description} activeNav="about">
|
<Layout title="关于" description={Description}>
|
||||||
<section class="vh-container">
|
<section class="vh-container">
|
||||||
<section class="vh-about">
|
<section class="vh-about vh-animation vh-animation-init">
|
||||||
<header class="vh-page-header">
|
<header class="vh-page-header">
|
||||||
<h1>关于我</h1>
|
<h1>关于我</h1>
|
||||||
<p>Hi there, I’m Han 👋</p>
|
<p>Hi there, I’m Han 👋</p>
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import Aside from "../../components/Aside.astro";
|
|||||||
import Archive from "../../components/Archive.astro";
|
import Archive from "../../components/Archive.astro";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title="归档" description={Description} activeNav="archives">
|
<Layout title="归档" description={Description}>
|
||||||
<section class="vh-container">
|
<section class="vh-container">
|
||||||
<Archive articleList={articleList} />
|
<Archive articleList={articleList} />
|
||||||
<Aside />
|
<Aside />
|
||||||
|
|||||||
@ -14,10 +14,12 @@ import getCover from "../../utils/getCover";
|
|||||||
const ARTICLE_COVER: string = await getCover(post.data.cover);
|
const ARTICLE_COVER: string = await getCover(post.data.cover);
|
||||||
// 页面 Info
|
// 页面 Info
|
||||||
import SITE_CONFIG from "../../config";
|
import SITE_CONFIG from "../../config";
|
||||||
const { Site, Title, Author, Twikoo } = SITE_CONFIG;
|
const { Site, Title, Author, Twikoo, GoogleAds } = SITE_CONFIG;
|
||||||
// 处理文章内容
|
// 处理文章内容
|
||||||
const description = getDescription(post);
|
const description = getDescription(post);
|
||||||
const { Content } = await render(post);
|
const { Content, remarkPluginFrontmatter } = await render(post);
|
||||||
|
// 文章字数和阅读时间
|
||||||
|
const { reading_time, article_word_count } = remarkPluginFrontmatter;
|
||||||
// 公共 Layout
|
// 公共 Layout
|
||||||
import Layout from "../../layouts/Layout.astro";
|
import Layout from "../../layouts/Layout.astro";
|
||||||
// Aside组件
|
// Aside组件
|
||||||
@ -26,19 +28,23 @@ import Aside from "../../components/Aside.astro";
|
|||||||
import Copyright from "../../components/Copyright.astro";
|
import Copyright from "../../components/Copyright.astro";
|
||||||
// 评论组件
|
// 评论组件
|
||||||
import Comment from "../../components/Comment.astro";
|
import Comment from "../../components/Comment.astro";
|
||||||
|
// Google 广告组件
|
||||||
|
import GoogleAd from "../../components/GoogleAd.astro";
|
||||||
// 文章页面样式
|
// 文章页面样式
|
||||||
import "../../styles/Article.less";
|
import "../../styles/Article.less";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={post.data.title} keywords={post.data.tags} description={description} pagecover={ARTICLE_COVER}>
|
<Layout title={post.data.title} keywords={post.data.tags} description={description} pagecover={ARTICLE_COVER}>
|
||||||
<section class="vh-container">
|
<section class="vh-container">
|
||||||
<article class="vh-animation vh-article-main">
|
<article class="vh-article-main vh-animation vh-animation-init">
|
||||||
<header>
|
<header>
|
||||||
<h1>{post.data.title}</h1>
|
<h1>{post.data.title}</h1>
|
||||||
<div class="article-meta">
|
<div class="article-meta">
|
||||||
<span class="article-meta-item">
|
<span class="article-meta-item">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"> <path d="M152 24c0-13.3-10.7-24-24-24s-24 10.7-24 24l0 40L64 64C28.7 64 0 92.7 0 128l0 16 0 48L0 448c0 35.3 28.7 64 64 64l320 0c35.3 0 64-28.7 64-64l0-256 0-48 0-16c0-35.3-28.7-64-64-64l-40 0 0-40c0-13.3-10.7-24-24-24s-24 10.7-24 24l0 40L152 64l0-40zM48 192l80 0 0 56-80 0 0-56zm0 104l80 0 0 64-80 0 0-64zm128 0l96 0 0 64-96 0 0-64zm144 0l80 0 0 64-80 0 0-64zm80-48l-80 0 0-56 80 0 0 56zm0 160l0 40c0 8.8-7.2 16-16 16l-64 0 0-56 80 0zm-128 0l0 56-96 0 0-56 96 0zm-144 0l0 56-64 0c-8.8 0-16-7.2-16-16l0-40 80 0zM272 248l-96 0 0-56 96 0 0 56z"></path></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"> <path d="M152 24c0-13.3-10.7-24-24-24s-24 10.7-24 24l0 40L64 64C28.7 64 0 92.7 0 128l0 16 0 48L0 448c0 35.3 28.7 64 64 64l320 0c35.3 0 64-28.7 64-64l0-256 0-48 0-16c0-35.3-28.7-64-64-64l-40 0 0-40c0-13.3-10.7-24-24-24s-24 10.7-24 24l0 40L152 64l0-40zM48 192l80 0 0 56-80 0 0-56zm0 104l80 0 0 64-80 0 0-64zm128 0l96 0 0 64-96 0 0-64zm144 0l80 0 0 64-80 0 0-64zm80-48l-80 0 0-56 80 0 0 56zm0 160l0 40c0 8.8-7.2 16-16 16l-64 0 0-56 80 0zm-128 0l0 56-96 0 0-56 96 0zm-144 0l0 56-64 0c-8.8 0-16-7.2-16-16l0-40 80 0zM272 248l-96 0 0-56 96 0 0 56z"></path></svg>
|
||||||
<time>{fmtTime(post.data.date, "YYYY-MM-DD A")}</time>
|
<time>{fmtTime(post.data.date, "YYYY-MM-DD A")}</time>
|
||||||
|
<span class="count"><strong>{article_word_count}</strong>字</span>
|
||||||
|
<span class="time"><strong>{parseFloat((Number(reading_time) || 0).toFixed(1).replace(/\.0+$/, ""))}</strong>分钟</span>
|
||||||
</span>
|
</span>
|
||||||
<a class="article-meta-item" href={`/categories/${post.data.categories}`}>
|
<a class="article-meta-item" href={`/categories/${post.data.categories}`}>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <path d="M40 48C26.7 48 16 58.7 16 72l0 48c0 13.3 10.7 24 24 24l48 0c13.3 0 24-10.7 24-24l0-48c0-13.3-10.7-24-24-24L40 48zM192 64c-17.7 0-32 14.3-32 32s14.3 32 32 32l288 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L192 64zm0 160c-17.7 0-32 14.3-32 32s14.3 32 32 32l288 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-288 0zm0 160c-17.7 0-32 14.3-32 32s14.3 32 32 32l288 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-288 0zM16 232l0 48c0 13.3 10.7 24 24 24l48 0c13.3 0 24-10.7 24-24l0-48c0-13.3-10.7-24-24-24l-48 0c-13.3 0-24 10.7-24 24zM40 368c-13.3 0-24 10.7-24 24l0 48c0 13.3 10.7 24 24 24l48 0c13.3 0 24-10.7 24-24l0-48c0-13.3-10.7-24-24-24l-48 0z"></path></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <path d="M40 48C26.7 48 16 58.7 16 72l0 48c0 13.3 10.7 24 24 24l48 0c13.3 0 24-10.7 24-24l0-48c0-13.3-10.7-24-24-24L40 48zM192 64c-17.7 0-32 14.3-32 32s14.3 32 32 32l288 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L192 64zm0 160c-17.7 0-32 14.3-32 32s14.3 32 32 32l288 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-288 0zm0 160c-17.7 0-32 14.3-32 32s14.3 32 32 32l288 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-288 0zM16 232l0 48c0 13.3 10.7 24 24 24l48 0c13.3 0 24-10.7 24-24l0-48c0-13.3-10.7-24-24-24l-48 0c-13.3 0-24 10.7-24 24zM40 368c-13.3 0-24 10.7-24 24l0 48c0 13.3 10.7 24 24 24l48 0c13.3 0 24-10.7 24-24l0-48c0-13.3-10.7-24-24-24l-48 0z"></path></svg>
|
||||||
@ -49,41 +55,16 @@ import "../../styles/Article.less";
|
|||||||
<main>
|
<main>
|
||||||
<Content />
|
<Content />
|
||||||
<nav class="tag-list">
|
<nav class="tag-list">
|
||||||
{post.data.tags.map((i: any) => <a href={`/tags/${i}`}>{i}</a>)}
|
{post.data.tags.map((i: any) => <a href={`/tag/${i}`}>{i}</a>)}
|
||||||
</nav>
|
</nav>
|
||||||
</main>
|
</main>
|
||||||
<footer>
|
<footer>
|
||||||
|
<!-- 底部谷歌广告 -->
|
||||||
|
{GoogleAds.ad_Client && GoogleAds.articleAD_Slot && <GoogleAd className="vh-article-ad" slotID={GoogleAds.articleAD_Slot} />}
|
||||||
<Copyright site={Site} id={post.data.id} title={post.data.title} sitename={Title} time={fmtTime(post.data.date, "YYYY-MM-DD A")} auther={Author} />
|
<Copyright site={Site} id={post.data.id} title={post.data.title} sitename={Title} time={fmtTime(post.data.date, "YYYY-MM-DD A")} auther={Author} />
|
||||||
</footer>
|
</footer>
|
||||||
{Twikoo.envId && <Comment envId={Twikoo.envId} />}
|
{Twikoo.envId && <Comment envId={Twikoo.envId} />}
|
||||||
</article>
|
</article>
|
||||||
<Aside />
|
<Aside />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script>
|
|
||||||
import updateRouter from "../../utils/updateRouter";
|
|
||||||
// 初始化文章功能脚本
|
|
||||||
import ArticleInit from "../../scripts/Article";
|
|
||||||
// 初始化视频播放器
|
|
||||||
import videoInit from "../../scripts/Video";
|
|
||||||
// 初始化音乐播放器
|
|
||||||
import musicInit from "../../scripts/Music";
|
|
||||||
// 进入页面时初始化
|
|
||||||
const videoList: any[] = [];
|
|
||||||
const MusicList: any[] = [];
|
|
||||||
updateRouter("afterMount", () => {
|
|
||||||
ArticleInit();
|
|
||||||
videoInit(videoList);
|
|
||||||
musicInit(MusicList);
|
|
||||||
});
|
|
||||||
// 页面离开卸载播放器
|
|
||||||
updateRouter("beforeCreate", () => {
|
|
||||||
// 销毁播放器
|
|
||||||
videoList.forEach((i: any) => i.destroy());
|
|
||||||
videoList.length = 0;
|
|
||||||
// 销毁音乐
|
|
||||||
MusicList.forEach((i: any) => i.destroy());
|
|
||||||
MusicList.length = 0;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import Aside from "../../components/Aside.astro";
|
|||||||
import Archive from "../../components/Archive.astro";
|
import Archive from "../../components/Archive.astro";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={`分类 ${categories}`} description={Description}>
|
<Layout title={`分类 ${categories} 下的文章`} description={Description}>
|
||||||
<section class="vh-container">
|
<section class="vh-container">
|
||||||
<Archive articleList={articleList} />
|
<Archive articleList={articleList} />
|
||||||
<Aside />
|
<Aside />
|
||||||
|
|||||||
@ -12,26 +12,16 @@ import Comment from "../../components/Comment.astro";
|
|||||||
import "../../styles/Links.less";
|
import "../../styles/Links.less";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title="友情链接" description={Description} activeNav="links">
|
<Layout title="友情链接" description={Description}>
|
||||||
<section class="vh-container">
|
<section class="vh-container">
|
||||||
<section class="vh-links">
|
<section class="vh-links vh-animation vh-animation-init">
|
||||||
<header class="vh-page-header">
|
<header class="vh-page-header">
|
||||||
<h1>朋友圈 👭</h1>
|
<h1>朋友圈 👭</h1>
|
||||||
<p>天下快意之事莫若友。</p>
|
<p>天下快意之事莫若友。</p>
|
||||||
</header>
|
</header>
|
||||||
<main></main>
|
<main><section class="vh-space-loading"><span></span><span></span><span></span></section></main>
|
||||||
{Twikoo.envId && <Comment envId={Twikoo.envId} />}
|
{Twikoo.envId && <Comment envId={Twikoo.envId} />}
|
||||||
</section>
|
</section>
|
||||||
<Aside />
|
<Aside />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script>
|
|
||||||
import updateRouter from "../../utils/updateRouter";
|
|
||||||
import LinksInit from "../../scripts/Links";
|
|
||||||
// 进入页面时初始化
|
|
||||||
// 数据源
|
|
||||||
import LINKS_DATA from "../../page_data/Link";
|
|
||||||
const { api, data } = LINKS_DATA;
|
|
||||||
updateRouter("afterMount", () => LinksInit(api || data));
|
|
||||||
</script>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@ -10,9 +10,9 @@ import Layout from "../../layouts/Layout.astro";
|
|||||||
import Comment from "../../components/Comment.astro";
|
import Comment from "../../components/Comment.astro";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title="留言" description={Description} activeNav="message">
|
<Layout title="留言" description={Description}>
|
||||||
<section class="vh-container">
|
<section class="vh-container">
|
||||||
<section class="vh-message" style="gap:1.25rem;">
|
<section class="vh-message vh-animation vh-animation-init" style="gap:1.25rem;">
|
||||||
<header class="vh-page-header">
|
<header class="vh-page-header">
|
||||||
<h1>留言板 🌸</h1>
|
<h1>留言板 🌸</h1>
|
||||||
<p>快友之事莫若谈。</p>
|
<p>快友之事莫若谈。</p>
|
||||||
@ -21,11 +21,4 @@ import Comment from "../../components/Comment.astro";
|
|||||||
</section>
|
</section>
|
||||||
<Aside />
|
<Aside />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script>
|
|
||||||
import updateRouter from "../../utils/updateRouter";
|
|
||||||
import LinksInit from "../../scripts/Links";
|
|
||||||
// 进入页面时初始化
|
|
||||||
updateRouter("afterMount", LinksInit);
|
|
||||||
</script>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@ -11,7 +11,7 @@ export async function GET(context: any) {
|
|||||||
title: Title,
|
title: Title,
|
||||||
description: Description,
|
description: Description,
|
||||||
site: context.site,
|
site: context.site,
|
||||||
items: posts.map((post) => ({
|
items: posts.filter(i => !i.data.hide).map((post) => ({
|
||||||
title: post.data.title,
|
title: post.data.title,
|
||||||
pubDate: post.data.updated || post.data.date,
|
pubDate: post.data.updated || post.data.date,
|
||||||
description: getDescription(post),
|
description: getDescription(post),
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import Aside from "../../components/Aside.astro";
|
|||||||
import Archive from "../../components/Archive.astro";
|
import Archive from "../../components/Archive.astro";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={`标签 ${tags}`} description={Description}>
|
<Layout title={`标签 ${tags} 下的文章`} description={Description}>
|
||||||
<section class="vh-container">
|
<section class="vh-container">
|
||||||
<Archive articleList={articleList} />
|
<Archive articleList={articleList} />
|
||||||
<Aside />
|
<Aside />
|
||||||
@ -12,26 +12,16 @@ import Comment from "../../components/Comment.astro";
|
|||||||
import "../../styles/Talking.less";
|
import "../../styles/Talking.less";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title="动态" description={Description} activeNav="talking">
|
<Layout title="动态" description={Description}>
|
||||||
<section class="vh-container">
|
<section class="vh-container">
|
||||||
<section class="vh-talking">
|
<section class="vh-talking vh-animation vh-animation-init">
|
||||||
<header class="vh-page-header">
|
<header class="vh-page-header">
|
||||||
<h1>动态 🥫</h1>
|
<h1>动态 🥫</h1>
|
||||||
<p>记录美好生活.</p>
|
<p>记录美好生活.</p>
|
||||||
</header>
|
</header>
|
||||||
<main></main>
|
<main><section class="vh-space-loading white"><span></span><span></span><span></span></section></main>
|
||||||
{Twikoo.envId && <Comment envId={Twikoo.envId} />}
|
{Twikoo.envId && <Comment envId={Twikoo.envId} />}
|
||||||
</section>
|
</section>
|
||||||
<Aside />
|
<Aside />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script>
|
|
||||||
import updateRouter from "../../utils/updateRouter";
|
|
||||||
import TalkingInit from "../../scripts/Talking";
|
|
||||||
// 进入页面时初始化
|
|
||||||
// 数据源
|
|
||||||
import TALKING_DATA from "../../page_data/Talking";
|
|
||||||
const { api, data } = TALKING_DATA;
|
|
||||||
updateRouter("afterMount", () => TalkingInit(api || data));
|
|
||||||
</script>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@ -1,15 +1,17 @@
|
|||||||
// src/plugins/remark-note.js
|
// src/plugins/remark-note.js
|
||||||
import { visit } from 'unist-util-visit';
|
import { visit } from 'unist-util-visit';
|
||||||
|
import getReadingTime from 'reading-time';
|
||||||
|
import { toString } from 'mdast-util-to-string';
|
||||||
|
|
||||||
// 处理函数
|
// 处理函数
|
||||||
const nodeTreeFmt = (node: any, parent: any) => {
|
const nodeTreeFmt = (node: any, parent: any) => {
|
||||||
if (node.type === 'text') parent.children = node.value.split('\n').map((i: string) => ({ type: 'paragraph', children: [{ type: 'text', value: i }] }));
|
if (node.type === 'text') parent.children = node.value.split('\n').map((i: string) => ({ type: 'paragraph', children: [{ type: 'text', value: i }] }));
|
||||||
if (node.children && node.children.length > 0) node.children.forEach((child: any) => nodeTreeFmt(child, node));
|
if (node.children && node.children.length) node.children.forEach((child: any) => nodeTreeFmt(child, node));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理标签
|
// 处理标签
|
||||||
const remarkNote = () => {
|
const remarkNote = () => {
|
||||||
return (tree: any) => {
|
return (tree: any, { data: astroData }: any) => {
|
||||||
visit(tree, (node, index, parent) => {
|
visit(tree, (node, index, parent) => {
|
||||||
const { type, name, attributes } = node;
|
const { type, name, attributes } = node;
|
||||||
// 处理组件
|
// 处理组件
|
||||||
@ -35,6 +37,11 @@ const remarkNote = () => {
|
|||||||
};
|
};
|
||||||
// 设置 class
|
// 设置 class
|
||||||
hProperties.class = `vh-node vh-${name}${attributes.type ? ` ${name}-${attributes.type}` : ''}`;
|
hProperties.class = `vh-node vh-${name}${attributes.type ? ` ${name}-${attributes.type}` : ''}`;
|
||||||
|
// 文章字数统计
|
||||||
|
const textOnPage = toString(tree);
|
||||||
|
const readingTime = getReadingTime(textOnPage);
|
||||||
|
astroData.astro.frontmatter.reading_time = readingTime.minutes
|
||||||
|
astroData.astro.frontmatter.article_word_count = readingTime.words
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,7 +9,9 @@ import { OverlayScrollbars } from "overlayscrollbars";
|
|||||||
// Pre Code 代码复制功能======
|
// Pre Code 代码复制功能======
|
||||||
let copyText = null;
|
let copyText = null;
|
||||||
// Pre Code 代码复制功能======
|
// Pre Code 代码复制功能======
|
||||||
const ArticleInit = () => {
|
|
||||||
|
// 初始化
|
||||||
|
export default () => {
|
||||||
// 灯箱JS初始化======
|
// 灯箱JS初始化======
|
||||||
ViewImage && ViewImage.init("main>.vh-container>article.vh-article-main img.vh-article-img");
|
ViewImage && ViewImage.init("main>.vh-container>article.vh-article-main img.vh-article-img");
|
||||||
// 灯箱JS初始化======
|
// 灯箱JS初始化======
|
||||||
@ -34,5 +36,4 @@ const ArticleInit = () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
// Pre Code 代码复制功能======
|
// Pre Code 代码复制功能======
|
||||||
}
|
}
|
||||||
export default ArticleInit;
|
|
||||||
@ -19,7 +19,9 @@ let backTop: any = document.querySelector(".vh-back-top");
|
|||||||
// 彩虹圈圈 DOM
|
// 彩虹圈圈 DOM
|
||||||
let circle: any = document.querySelector(".vh-back-top>svg>circle");
|
let circle: any = document.querySelector(".vh-back-top>svg>circle");
|
||||||
const circumference = 2 * Math.PI * 10;
|
const circumference = 2 * Math.PI * 10;
|
||||||
const BackTopInitFn = () => {
|
|
||||||
|
// 初始化
|
||||||
|
export default () => {
|
||||||
// 更新 彩虹圈圈 DOM
|
// 更新 彩虹圈圈 DOM
|
||||||
circle = document.querySelector(".vh-back-top>svg>circle");
|
circle = document.querySelector(".vh-back-top>svg>circle");
|
||||||
// 更新 回顶部DOM
|
// 更新 回顶部DOM
|
||||||
@ -38,5 +40,3 @@ const BackTopInitFn = () => {
|
|||||||
// 触发 scrollChangeFn
|
// 触发 scrollChangeFn
|
||||||
scrollChangeFn();
|
scrollChangeFn();
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BackTopInitFn;
|
|
||||||
8
src/scripts/Comment.ts
Normal file
8
src/scripts/Comment.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import SITE_INFO from "../config";
|
||||||
|
declare const twikoo: any;
|
||||||
|
// 初始化评论插件
|
||||||
|
export default async () => {
|
||||||
|
const commentDOM = '.vh-comment>section'
|
||||||
|
if (!document.querySelector(commentDOM) || !SITE_INFO.Twikoo.envId) return;
|
||||||
|
twikoo.init({ envId: SITE_INFO.Twikoo.envId, el: commentDOM })
|
||||||
|
}
|
||||||
7
src/scripts/Footer.ts
Normal file
7
src/scripts/Footer.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// 格式化时间
|
||||||
|
import { fmtDate } from "../utils/index";
|
||||||
|
// 页面内容的元数据
|
||||||
|
import SITE_CONFIG from "../config";
|
||||||
|
const { CreateTime } = SITE_CONFIG;
|
||||||
|
// 初始化 网站运行时间
|
||||||
|
export default () => (document.querySelector("em.web_time")!.textContent = fmtDate(CreateTime))
|
||||||
24
src/scripts/GoogleAd.ts
Normal file
24
src/scripts/GoogleAd.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
// 声明全局变量 adsbygoogle
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
adsbygoogle: any[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
import SITE_INFO from '../config'
|
||||||
|
const { GoogleAds } = SITE_INFO
|
||||||
|
export default () => {
|
||||||
|
const asideAD: any = document.querySelector('.vh-aside-ad')
|
||||||
|
const articleAD: any = document.querySelector('.vh-article-ad')
|
||||||
|
if (!asideAD && !articleAD) return;
|
||||||
|
// 初始化侧边栏广告
|
||||||
|
if (asideAD) {
|
||||||
|
asideAD.innerHTML = GoogleAds.asideAD_Slot;
|
||||||
|
(window.adsbygoogle = window.adsbygoogle || []).push({})
|
||||||
|
}
|
||||||
|
// 初始化文章页广告
|
||||||
|
if (articleAD) {
|
||||||
|
articleAD.innerHTML = GoogleAds.articleAD_Slot;
|
||||||
|
(window.adsbygoogle = window.adsbygoogle || []).push({})
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/scripts/Header.ts
Normal file
11
src/scripts/Header.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
const linkListArr = ['links', 'talking', 'archives', 'message', 'about']
|
||||||
|
export default () => {
|
||||||
|
const linkARR = document.querySelectorAll('.vh-link-list>a');
|
||||||
|
if (!linkARR.length) return;
|
||||||
|
linkARR.forEach((i: any) => {
|
||||||
|
i.classList.remove('active');
|
||||||
|
const linkName = (window.location.pathname).split('/')[1]
|
||||||
|
if (!linkListArr.includes(linkName)) return;
|
||||||
|
document.querySelectorAll(`.${linkName}`).forEach((i: any) => i.classList.add('active'));
|
||||||
|
})
|
||||||
|
}
|
||||||
91
src/scripts/Init.ts
Normal file
91
src/scripts/Init.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import { inRouter, outRouter } from "../utils/updateRouter";
|
||||||
|
// 初始化文章功能脚本
|
||||||
|
import ArticleInit from "../scripts/Article";
|
||||||
|
// 初始化视频播放器
|
||||||
|
import videoInit from "../scripts/Video";
|
||||||
|
// 初始化音乐播放器
|
||||||
|
import musicInit from "../scripts/Music";
|
||||||
|
// 初始化BackTop组件
|
||||||
|
import BackTopInitFn from "../scripts/BackTop";
|
||||||
|
// 搜索
|
||||||
|
import { searchFn, vhSearchInit } from "../scripts/Search";
|
||||||
|
// 图片懒加载
|
||||||
|
import vhLzImgInit from "../scripts/vhLazyImg";
|
||||||
|
// 顶部导航 Current 状态
|
||||||
|
import initLinkCurrent from "../scripts/Header";
|
||||||
|
// 底部网站运行时间
|
||||||
|
import initWebSiteTime from "../scripts/Footer";
|
||||||
|
// 友情链接初始化
|
||||||
|
import initLinks from "../scripts/Links";
|
||||||
|
// 动态说说初始化
|
||||||
|
import initTalking from "../scripts/Talking";
|
||||||
|
// 文章评论初始化
|
||||||
|
import initComment from "../scripts/Comment";
|
||||||
|
// 移动端侧边栏初始化
|
||||||
|
import initMobileSidebar from "../scripts/MobileSidebar";
|
||||||
|
// Google 广告
|
||||||
|
import GoogleAdInit from "../scripts/GoogleAd";
|
||||||
|
// Han Analytics 统计
|
||||||
|
import SITE_INFO from "../config";
|
||||||
|
const { HanAnalytics } = SITE_INFO;
|
||||||
|
import { LoadScript } from "../utils/index";
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// 页面初始化 Only
|
||||||
|
const videoList: any[] = [];
|
||||||
|
const MusicList: any[] = [];
|
||||||
|
const indexInit = async (only: boolean = true) => {
|
||||||
|
// 预加载搜索数据
|
||||||
|
only && searchFn("");
|
||||||
|
// 初始化搜索功能
|
||||||
|
only && vhSearchInit();
|
||||||
|
// 初始化网站运行时间
|
||||||
|
only && initWebSiteTime();
|
||||||
|
// 初始化BackTop组件
|
||||||
|
only && BackTopInitFn();
|
||||||
|
// 移动端侧边栏初始化
|
||||||
|
only && initMobileSidebar();
|
||||||
|
// 顶部导航 Current 状态
|
||||||
|
initLinkCurrent()
|
||||||
|
// 初始化文章功能脚本
|
||||||
|
ArticleInit();
|
||||||
|
// 图片懒加载初始化
|
||||||
|
vhLzImgInit();
|
||||||
|
// 友情链接初始化
|
||||||
|
initLinks();
|
||||||
|
// 动态说说初始化
|
||||||
|
initTalking();
|
||||||
|
// 文章评论初始化
|
||||||
|
initComment();
|
||||||
|
// Google 广告
|
||||||
|
GoogleAdInit();
|
||||||
|
// 文章视频播放器初始化
|
||||||
|
videoInit(videoList);
|
||||||
|
// 文章音乐播放器初始化
|
||||||
|
musicInit(MusicList);
|
||||||
|
// Han Analytics 统计
|
||||||
|
HanAnalytics.enable && LoadScript(`${HanAnalytics.server}/tracker.min.js`, [{ k: "data-website-id", v: HanAnalytics.siteId }]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
// 首次初始化
|
||||||
|
indexInit();
|
||||||
|
// 进入页面时触发
|
||||||
|
inRouter(() => indexInit(false));
|
||||||
|
// 离开当前页面时触发
|
||||||
|
outRouter(() => {
|
||||||
|
// 销毁播放器
|
||||||
|
videoList.forEach((i: any) => i.destroy());
|
||||||
|
videoList.length = 0;
|
||||||
|
// 销毁音乐
|
||||||
|
MusicList.forEach((i: any) => i.destroy());
|
||||||
|
MusicList.length = 0;
|
||||||
|
});
|
||||||
|
console.log(
|
||||||
|
"%c\u4E3B\u9898\uFF1AvhAstro-Theme%c https://github.com/uxiaohan/vhAstro-Theme ",
|
||||||
|
"background: linear-gradient(90deg, #030307, #1a1a2e); color: #fadfa3; padding: 4px;",
|
||||||
|
"background: #EDEDED; padding: 4px;"
|
||||||
|
);
|
||||||
|
console.log("%c\u521D\u59CB\u5316\u5B8C\u6BD5.", "color: #ffffff; background: #000; padding:5px");
|
||||||
|
}
|
||||||
@ -1,13 +1,11 @@
|
|||||||
|
|
||||||
import vh from 'vh-plugin'
|
import vh from 'vh-plugin'
|
||||||
import { $GET } from '../utils/index'
|
import { $GET } from '../utils/index'
|
||||||
// 图片懒加载
|
// 图片懒加载
|
||||||
import vhLzImgInit from "../scripts/vhLazyImg";
|
import vhLzImgInit from "../scripts/vhLazyImg";
|
||||||
|
// 渲染
|
||||||
const LinksInit = async (data: any) => {
|
const LinksInit = async (data: any) => {
|
||||||
const linksDOM = document.querySelector('.vh-container>.vh-links>main')
|
const linksDOM = document.querySelector('.vh-container>.vh-links>main')
|
||||||
if (!linksDOM) return;
|
if (!linksDOM) return;
|
||||||
vh.showLoading();
|
|
||||||
try {
|
try {
|
||||||
let res = data;
|
let res = data;
|
||||||
if (typeof data === 'string') {
|
if (typeof data === 'string') {
|
||||||
@ -18,8 +16,10 @@ const LinksInit = async (data: any) => {
|
|||||||
vhLzImgInit();
|
vhLzImgInit();
|
||||||
} catch {
|
} catch {
|
||||||
vh.Toast('获取数据失败')
|
vh.Toast('获取数据失败')
|
||||||
} finally {
|
|
||||||
vh.hideLoading();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default LinksInit;
|
|
||||||
|
// 友情链接初始化
|
||||||
|
import LINKS_DATA from "../page_data/Link";
|
||||||
|
const { api, data } = LINKS_DATA;
|
||||||
|
export default () => LinksInit(api || data)
|
||||||
9
src/scripts/MobileSidebar.ts
Normal file
9
src/scripts/MobileSidebar.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// 初始化侧边栏
|
||||||
|
export default () => {
|
||||||
|
const menuDOM: any = document.querySelector(".vh-header>.main>.nav-btn>span.menu-btn");
|
||||||
|
const mobileSidebarDOM: any = document.querySelector("body>.vh-mobilesidebar");
|
||||||
|
const addActive = () => setTimeout(() => mobileSidebarDOM.classList.add("active"));
|
||||||
|
const removeActive = () => setTimeout(() => mobileSidebarDOM.classList.remove("active"));
|
||||||
|
menuDOM.addEventListener("click", addActive);
|
||||||
|
mobileSidebarDOM.addEventListener("click", removeActive);
|
||||||
|
};
|
||||||
@ -4,19 +4,17 @@ import { $GET } from '../utils/index'
|
|||||||
import { LoadScript, LoadStyle } from "../utils/index";
|
import { LoadScript, LoadStyle } from "../utils/index";
|
||||||
|
|
||||||
declare const APlayer: any;
|
declare const APlayer: any;
|
||||||
const musicInit = async (musicList: any[]) => {
|
// 初始化音乐播放器
|
||||||
|
export default async (musicList: any[]) => {
|
||||||
const musicDOM: any = document.querySelectorAll(".vh-node.vh-vhMusic");
|
const musicDOM: any = document.querySelectorAll(".vh-node.vh-vhMusic");
|
||||||
if (musicDOM.length === 0) return;
|
if (musicDOM.length === 0) return;
|
||||||
// 载入依赖
|
// 载入依赖
|
||||||
await LoadStyle("https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/aplayer/1.10.1/APlayer.min.css");
|
await LoadStyle("https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/aplayer/1.10.1/APlayer.min.css");
|
||||||
await LoadScript("https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/aplayer/1.10.1/APlayer.min.js");
|
await LoadScript("https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/aplayer/1.10.1/APlayer.min.js");
|
||||||
musicDOM.forEach(async (container: any) => {
|
musicDOM.forEach(async (container: any) => {
|
||||||
const { type, id, list } = container.dataset;
|
const { type = 'song', server = 'netease', id } = container.dataset;
|
||||||
const audio = await $GET(`${vhMusicApi}?server=${type}&type=${id ? 'song' : 'playlist'}&id=${id ? id : list}&r=${Math.random()}`);
|
const audio = await $GET(`${vhMusicApi}?server=${server}&type=${type}&id=${id}&r=${Math.random()}`);
|
||||||
const ap = new APlayer({ container, audio, lrcType: 3 });
|
const ap = new APlayer({ container, audio, lrcType: 3 });
|
||||||
musicList.push(ap);
|
musicList.push(ap);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default musicInit;
|
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ const getSearchJson = async () => (searchJson = await $GET('/vh-search.json'))
|
|||||||
|
|
||||||
// 搜索
|
// 搜索
|
||||||
const searchFn = async (value: string) => {
|
const searchFn = async (value: string) => {
|
||||||
if (searchJson.length < 1) await getSearchJson();
|
if (!searchJson.length) await getSearchJson();
|
||||||
// 渲染页面
|
// 渲染页面
|
||||||
renderSearch(findAndModifyElements(searchJson, value))
|
renderSearch(findAndModifyElements(searchJson, value))
|
||||||
}
|
}
|
||||||
@ -31,7 +31,7 @@ const findAndModifyElements = (arr: any[], keyword: string) => {
|
|||||||
// 渲染页面
|
// 渲染页面
|
||||||
let searchHTML = '';
|
let searchHTML = '';
|
||||||
const renderSearch = (arr: any[]) => {
|
const renderSearch = (arr: any[]) => {
|
||||||
searchHTML = arr.length < 1 ? '' : arr.map(i => `<a class="vh-search-item" href="${i.url}"><span>${i.title}</span><p>${i.content}</p></a>`).join('');
|
searchHTML = !arr.length ? '' : arr.map(i => `<a class="vh-search-item" href="${i.url}"><span>${i.title}</span><p>${i.content}</p></a>`).join('');
|
||||||
document.querySelector('.vh-header>.main>.vh-search>main>.vh-search-list')!.innerHTML = searchHTML;
|
document.querySelector('.vh-header>.main>.vh-search>main>.vh-search-list')!.innerHTML = searchHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,4 +43,19 @@ const searchInputChange = (v: any) => {
|
|||||||
fnTimer = setTimeout(() => searchFn(value), 266);
|
fnTimer = setTimeout(() => searchFn(value), 266);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { searchFn, searchInputChange };
|
// 初始化搜索框
|
||||||
|
const vhSearchInit = () => {
|
||||||
|
const searchDOM: any = document.querySelector(".vh-header>.main>.nav-btn>span.search-btn");
|
||||||
|
const searchMainDOM: any = document.querySelector(".vh-header>.main>.vh-search>main");
|
||||||
|
const searchListDOM: any = document.querySelector(".vh-header>.main>.vh-search");
|
||||||
|
const addActive = () => setTimeout(() => searchListDOM.classList.add("active"));
|
||||||
|
const removeActive = () => setTimeout(() => searchListDOM.classList.remove("active"));
|
||||||
|
// 禁止默认事件
|
||||||
|
searchMainDOM.addEventListener("click", (e: Event) => e.stopPropagation());
|
||||||
|
searchDOM.addEventListener("click", addActive);
|
||||||
|
searchListDOM.addEventListener("click", removeActive);
|
||||||
|
// 搜索框初内容变化
|
||||||
|
searchListDOM.querySelector(".search-input>input").addEventListener("input", searchInputChange);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { searchFn, searchInputChange, vhSearchInit };
|
||||||
@ -12,7 +12,6 @@ declare const ViewImage: any;
|
|||||||
const TalkingInit = async (data: any) => {
|
const TalkingInit = async (data: any) => {
|
||||||
const talkingDOM = document.querySelector('.vh-container>.vh-talking>main')
|
const talkingDOM = document.querySelector('.vh-container>.vh-talking>main')
|
||||||
if (!talkingDOM) return;
|
if (!talkingDOM) return;
|
||||||
vh.showLoading();
|
|
||||||
try {
|
try {
|
||||||
let res = data;
|
let res = data;
|
||||||
if (typeof data === 'string') {
|
if (typeof data === 'string') {
|
||||||
@ -26,8 +25,11 @@ const TalkingInit = async (data: any) => {
|
|||||||
// 灯箱JS初始化======
|
// 灯箱JS初始化======
|
||||||
} catch {
|
} catch {
|
||||||
vh.Toast('获取数据失败')
|
vh.Toast('获取数据失败')
|
||||||
} finally {
|
|
||||||
vh.hideLoading();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default TalkingInit;
|
|
||||||
|
|
||||||
|
// 动态说说初始化
|
||||||
|
import TALKING_DATA from "../page_data/Talking";
|
||||||
|
const { api, data } = TALKING_DATA;
|
||||||
|
export default () => TalkingInit(api || data);
|
||||||
@ -2,7 +2,8 @@ import { LoadScript } from "../utils/index";
|
|||||||
// 初始化视频播放器
|
// 初始化视频播放器
|
||||||
declare const DPlayer: any;
|
declare const DPlayer: any;
|
||||||
declare const Hls: any;
|
declare const Hls: any;
|
||||||
const videoInit = async (videoList: any[]) => {
|
// 初始化视频播放器
|
||||||
|
export default async (videoList: any[]) => {
|
||||||
const videoDOM: any = document.querySelectorAll(".vh-node.vh-vhVideo");
|
const videoDOM: any = document.querySelectorAll(".vh-node.vh-vhVideo");
|
||||||
if (videoDOM.length === 0) return;
|
if (videoDOM.length === 0) return;
|
||||||
// 载入依赖
|
// 载入依赖
|
||||||
@ -32,6 +33,4 @@ const videoInit = async (videoList: any[]) => {
|
|||||||
});
|
});
|
||||||
videoList.push(dp);
|
videoList.push(dp);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default videoInit;
|
|
||||||
@ -1,8 +1,10 @@
|
|||||||
|
|
||||||
// 图片懒加载
|
// 图片懒加载
|
||||||
import LazyLoad from "vanilla-lazyload";
|
import LazyLoad from "vanilla-lazyload";
|
||||||
const vhLzImgInit = () => {
|
|
||||||
document.querySelectorAll("main>.vh-container>:not(aside) img:not(.view-image-container)").forEach((i: any) => {
|
// 初始化图片懒加载
|
||||||
|
export default () => {
|
||||||
|
document.querySelectorAll("main>.vh-container img:not(.view-image-container)").forEach((i: any) => {
|
||||||
// 是否包含data-vh-lz-src
|
// 是否包含data-vh-lz-src
|
||||||
if (!i.hasAttribute("data-vh-lz-src")) {
|
if (!i.hasAttribute("data-vh-lz-src")) {
|
||||||
i.setAttribute("data-vh-lz-src", i.getAttribute("src"));
|
i.setAttribute("data-vh-lz-src", i.getAttribute("src"));
|
||||||
@ -14,6 +16,4 @@ const vhLzImgInit = () => {
|
|||||||
threshold: 0,
|
threshold: 0,
|
||||||
data_src: "vh-lz-src"
|
data_src: "vh-lz-src"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default vhLzImgInit;
|
|
||||||
@ -43,6 +43,16 @@ section.vh-container {
|
|||||||
&>time {
|
&>time {
|
||||||
color: #9A9A9A;
|
color: #9A9A9A;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&>span {
|
||||||
|
&.count {
|
||||||
|
color: #3FA67F;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.time {
|
||||||
|
color: #E9B740;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,6 +121,7 @@ section.vh-container {
|
|||||||
height: max-content;
|
height: max-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 文章内容样式开始=========================
|
// 文章内容样式开始=========================
|
||||||
h1,
|
h1,
|
||||||
h2,
|
h2,
|
||||||
@ -169,8 +180,8 @@ section.vh-container {
|
|||||||
// p标签样式
|
// p标签样式
|
||||||
// ul标签样式
|
// ul标签样式
|
||||||
p,
|
p,
|
||||||
ul,
|
ul:not(.vh-vhMusic ul),
|
||||||
ol {
|
ol:not(.vh-vhMusic ol) {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0.618rem 0;
|
padding: 0.618rem 0;
|
||||||
font-size: 0.9375rem;
|
font-size: 0.9375rem;
|
||||||
@ -200,9 +211,25 @@ section.vh-container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ul,
|
ul:not(.vh-vhMusic ul),
|
||||||
ol {
|
ol:not(.vh-vhMusic ol) {
|
||||||
|
box-sizing: border-box;
|
||||||
padding-left: 1.25rem;
|
padding-left: 1.25rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
padding: 0.188rem 0.58rem;
|
||||||
|
border-left: 5px solid #eee;
|
||||||
|
border-left-color: #929292;
|
||||||
|
background: #f3f5f7;
|
||||||
|
|
||||||
|
&>p {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 全局表格样式 */
|
/* 全局表格样式 */
|
||||||
@ -223,7 +250,7 @@ section.vh-container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
th {
|
th {
|
||||||
text-align: left;
|
text-align: center;
|
||||||
background-color: #f8f9fa;
|
background-color: #f8f9fa;
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
border-bottom: solid 1px #EEEEEE;
|
border-bottom: solid 1px #EEEEEE;
|
||||||
@ -281,6 +308,40 @@ section.vh-container {
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
background: #f7f7f7;
|
background: #f7f7f7;
|
||||||
|
|
||||||
|
&.note-success {
|
||||||
|
border-color: #01C4B6;
|
||||||
|
border-left: 5px solid #01C4B6;
|
||||||
|
border-left-color: #01C4B6;
|
||||||
|
background: #01C4B618;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.note-info {
|
||||||
|
border-color: #3253b4;
|
||||||
|
border-left: 5px solid #3253b4;
|
||||||
|
border-left-color: #3253b4;
|
||||||
|
background: #3253b418;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.note-warning {
|
||||||
|
border-color: #DD8636;
|
||||||
|
border-left: 5px solid #DD8636;
|
||||||
|
border-left-color: #DD8636;
|
||||||
|
background: #DD863618;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.note-error {
|
||||||
|
border-color: #DE3C3D;
|
||||||
|
border-left: 5px solid #DE3C3D;
|
||||||
|
border-left-color: #DE3C3D;
|
||||||
|
background: #DE3C3D18;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.note-import {
|
||||||
|
border-left: 5px solid #B984DF;
|
||||||
|
border-left-color: #B984DF;
|
||||||
|
background: #B984DF18;
|
||||||
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
line-height: 2;
|
line-height: 2;
|
||||||
@ -363,14 +424,14 @@ section.vh-container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.btn-info {
|
&.btn-info {
|
||||||
border-color: #3275B4;
|
border-color: #3253b4;
|
||||||
|
|
||||||
&>span {
|
&>span {
|
||||||
color: #3275B4;
|
color: #3253b4;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
background-color: #3275B4;
|
background-color: #3253b4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -397,6 +458,18 @@ section.vh-container {
|
|||||||
background-color: #DE3C3D;
|
background-color: #DE3C3D;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.btn-import {
|
||||||
|
border-color: #B984DF;
|
||||||
|
|
||||||
|
&>span {
|
||||||
|
color: #B984DF;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: #B984DF;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// :::picture
|
// :::picture
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
@import url('/assets/font/index.css');
|
@import url('/assets/font/index.css');
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--vh-animation-delay: 0ms;
|
|
||||||
--vh-padding-top: calc(66px + 1rem);
|
--vh-padding-top: calc(66px + 1rem);
|
||||||
--vh-main-max-width: 1388px;
|
--vh-main-max-width: 1388px;
|
||||||
--vh-back-top: calc((calc(100vw - 2rem) - min((100vw - 2rem), var(--vh-main-max-width))) / 2 + 1rem);
|
--vh-back-top: calc((calc(100vw - 2rem) - min((100vw - 2rem), var(--vh-main-max-width))) / 2 + 1rem);
|
||||||
@ -18,6 +17,11 @@ blockquote {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
color: #333;
|
color: #333;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
text-decoration: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
@ -41,15 +45,12 @@ code {
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul,
|
img {
|
||||||
ol {
|
max-width: 100%;
|
||||||
display: flex;
|
max-height: 100%;
|
||||||
flex-direction: column;
|
overflow: hidden;
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
* {
|
* {
|
||||||
// IOS 点击阴影
|
// IOS 点击阴影
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
@ -103,26 +104,6 @@ html {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 动画效果
|
|
||||||
.vh-animation {
|
|
||||||
opacity: 0;
|
|
||||||
animation: 300ms fade-in-up;
|
|
||||||
animation-fill-mode: forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fade-in-up {
|
|
||||||
0% {
|
|
||||||
transform: translateY(2rem);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
transform: translateY(0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置图片懒加载样式
|
// 设置图片懒加载样式
|
||||||
main>.vh-container {
|
main>.vh-container {
|
||||||
img[data-vh-lz-src] {
|
img[data-vh-lz-src] {
|
||||||
@ -134,23 +115,163 @@ main>.vh-container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 加载进度条样式
|
|
||||||
#nprogress {
|
|
||||||
.bar {
|
|
||||||
height: 0.28rem !important;
|
|
||||||
background: #01C4B6 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.peg {
|
|
||||||
box-shadow: 0 0 10px #01C4B6, 0 0 5px #01C4B6 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
// 手机端 禁止选择
|
// 手机端 禁止选择
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
-ms-user-select: none;
|
-ms-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 首次加载效果
|
||||||
|
.vh-animation.vh-animation-init {
|
||||||
|
opacity: 0;
|
||||||
|
animation: 300ms vh-init-show;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
transition: opacity 0.16s ease-in-out, transform 0.16s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// swup 动画效果
|
||||||
|
html.swup-enabled,
|
||||||
|
html.is-changing {
|
||||||
|
.vh-animation {
|
||||||
|
&.vh-animation-init {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html.is-animating {
|
||||||
|
.vh-animation {
|
||||||
|
&.vh-animation-init {
|
||||||
|
transform: translateY(0.88rem);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 占位加载
|
||||||
|
.vh-space-loading {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
grid-column: span 3;
|
||||||
|
width: 100%;
|
||||||
|
height: 8.88rem;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&.white {
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 3px 8px 6px rgba(7, 17, 27, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
&>span {
|
||||||
|
background: #000;
|
||||||
|
position: relative;
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
margin: 3px;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
&:nth-of-type(1) {
|
||||||
|
-webkit-animation: scale .8s ease infinite;
|
||||||
|
animation: vh-loading-animation .8s ease infinite
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-of-type(2) {
|
||||||
|
-webkit-animation: scale .8s ease .2s infinite;
|
||||||
|
animation: vh-loading-animation .8s ease .2s infinite
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-of-type(3) {
|
||||||
|
-webkit-animation: scale .8s ease .4s infinite;
|
||||||
|
animation: vh-loading-animation .8s ease .4s infinite
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 谷歌广告模块
|
||||||
|
|
||||||
|
.vh-ad {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: max-content;
|
||||||
|
min-height: 5.88rem;
|
||||||
|
overflow: hidden !important;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
&.vh-article-ad {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0.75rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 3px 8px 6px rgba(7, 17, 27, 0.05);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
top: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 30%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
content: '广告加载中...';
|
||||||
|
color: #e3e4e6;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@keyframes vh-init-show {
|
||||||
|
0% {
|
||||||
|
transform: translateY(2rem);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes vh-loading-animation {
|
||||||
|
50% {
|
||||||
|
height: 25px
|
||||||
|
}
|
||||||
|
|
||||||
|
0% {
|
||||||
|
height: 5px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes vh-init-show {
|
||||||
|
0% {
|
||||||
|
transform: translateY(2rem);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes vh-loading-animation {
|
||||||
|
50% {
|
||||||
|
height: 25px
|
||||||
|
}
|
||||||
|
|
||||||
|
0% {
|
||||||
|
height: 5px
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -108,32 +108,35 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
&>footer {
|
&>footer {
|
||||||
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.5rem;
|
gap: 0.58rem;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
&>span {
|
&>span {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 0 0.5rem;
|
padding: 0.28rem 0.68rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 1.68rem;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
line-height: 1.46rem;
|
border: 1px solid #3253b4;
|
||||||
border: 1px solid #49b1f5;
|
border-radius: 0.88rem;
|
||||||
border-radius: 2rem;
|
background-color: #fff;
|
||||||
color: #49b1f5;
|
|
||||||
font-size: 0.72rem;
|
font-size: 0.72rem;
|
||||||
|
color: #3253b4;
|
||||||
transition: all .2s ease-in-out;
|
transition: all .2s ease-in-out;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
animation: talking-tag-active 0.16s ease-in-out infinite;
|
animation: talking-tag-active 0.16s ease-in-out infinite;
|
||||||
color: #fff;
|
|
||||||
background-color: #49b1f5;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ section.article-list {
|
|||||||
position: relative;
|
position: relative;
|
||||||
padding-bottom: 6rem;
|
padding-bottom: 6rem;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
|
grid-template-columns: repeat(3, 1fr);
|
||||||
gap: 1.6rem;
|
gap: 1.6rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: max-content;
|
height: max-content;
|
||||||
@ -12,7 +12,8 @@ section.article-list {
|
|||||||
&>.vh-article-item {
|
&>.vh-article-item {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: initial;
|
||||||
|
min-height: max-content;
|
||||||
border: solid 1px #eee;
|
border: solid 1px #eee;
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
@ -23,7 +24,8 @@ section.article-list {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: max-content;
|
||||||
|
min-height: 100%;
|
||||||
border-radius: 0.618rem;
|
border-radius: 0.618rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: background 0.16s;
|
transition: background 0.16s;
|
||||||
@ -53,6 +55,7 @@ section.article-list {
|
|||||||
|
|
||||||
|
|
||||||
&>.vh-article-desc {
|
&>.vh-article-desc {
|
||||||
|
flex: 1;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 1.25rem;
|
padding: 1.25rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -164,4 +167,16 @@ section.article-list {
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1150px) {
|
||||||
|
section.article-list {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 556px) {
|
||||||
|
section.article-list {
|
||||||
|
grid-template-columns: repeat(1, 1fr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,15 +1,24 @@
|
|||||||
aside.vh-aside {
|
aside.vh-aside {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
position: sticky;
|
|
||||||
top: var(--vh-padding-top);
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
width: 288px;
|
width: 288px;
|
||||||
height: max-content;
|
height: initial;
|
||||||
|
|
||||||
&>.vh-aside-item {
|
&>.sticky-aside {
|
||||||
|
position: sticky;
|
||||||
|
top: var(--vh-padding-top);
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
height: max-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vh-aside-item {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -17,6 +26,7 @@ aside.vh-aside {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
height: max-content;
|
height: max-content;
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
@ -24,7 +34,6 @@ aside.vh-aside {
|
|||||||
box-shadow: 0 3px 8px 6px rgba(7, 17, 27, 0.05);
|
box-shadow: 0 3px 8px 6px rgba(7, 17, 27, 0.05);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
|
||||||
&>h3 {
|
&>h3 {
|
||||||
padding-bottom: 0.618rem;
|
padding-bottom: 0.618rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -50,45 +59,9 @@ aside.vh-aside {
|
|||||||
&>.vh-aside-avatar {
|
&>.vh-aside-avatar {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 6.36rem;
|
width: 6.36rem;
|
||||||
aspect-ratio: 1/1;
|
height: 6.36rem;
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
cursor: pointer;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
&:hover {
|
|
||||||
&>img {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translate(-50%, -100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&>svg {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&>img,
|
|
||||||
&>svg {
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
transition: transform 0.28s, opacity 0.18s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&>img {
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&>svg {
|
|
||||||
width: 60%;
|
|
||||||
height: 60%;
|
|
||||||
transform: translate(-50%, 100%);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&>.vh-aside-auther {
|
&>.vh-aside-auther {
|
||||||
@ -211,7 +184,45 @@ aside.vh-aside {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 最新文章
|
// 标签
|
||||||
|
&.tags {
|
||||||
|
&>.vh-aside-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
height: max-content;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&>a {
|
||||||
|
width: max-content;
|
||||||
|
height: max-content;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
&>span {
|
||||||
|
background: #EDEEF3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&>span {
|
||||||
|
display: block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0 0.618rem;
|
||||||
|
width: max-content;
|
||||||
|
height: 1.75rem;
|
||||||
|
border-radius: 0.38rem;
|
||||||
|
background: #EDEEF388;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-style: normal;
|
||||||
|
line-height: 1.75rem;
|
||||||
|
transition: background 0.16s ease-in-out;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 推荐文章
|
||||||
&.articles {
|
&.articles {
|
||||||
&>.vh-aside-articles {
|
&>.vh-aside-articles {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -293,44 +304,6 @@ aside.vh-aside {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 标签
|
|
||||||
&.tags {
|
|
||||||
&>.vh-aside-tags {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.5rem;
|
|
||||||
width: 100%;
|
|
||||||
height: max-content;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
&>a {
|
|
||||||
width: max-content;
|
|
||||||
height: max-content;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
&>span {
|
|
||||||
background: #EDEEF3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&>span {
|
|
||||||
display: block;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 0 0.618rem;
|
|
||||||
width: max-content;
|
|
||||||
height: 1.75rem;
|
|
||||||
border-radius: 0.38rem;
|
|
||||||
background: #EDEEF388;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-style: normal;
|
|
||||||
line-height: 1.75rem;
|
|
||||||
transition: background 0.16s ease-in-out;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -45,10 +45,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
padding: 0.38rem 0;
|
|
||||||
color: #4c4948;
|
color: #4c4948;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
|
line-height: 28px;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
box-shadow: inset 0 -.12em #60a5fa;
|
box-shadow: inset 0 -.12em #60a5fa;
|
||||||
@ -234,8 +234,10 @@
|
|||||||
|
|
||||||
// 内容
|
// 内容
|
||||||
.tk-content {
|
.tk-content {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 0;
|
padding: 0.18rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
|
|||||||
@ -25,7 +25,7 @@ const getTags = () => {
|
|||||||
// 获取推荐文章 (给文章添加 recommend: true 字段)
|
// 获取推荐文章 (给文章添加 recommend: true 字段)
|
||||||
const getRecommendArticles = () => {
|
const getRecommendArticles = () => {
|
||||||
const recommendList = posts.filter(i => i.data.recommend).slice(0, 6);
|
const recommendList = posts.filter(i => i.data.recommend).slice(0, 6);
|
||||||
return (recommendList.length > 0 ? 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) }))
|
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 };
|
export { getCategories, getTags, getRecommendArticles };
|
||||||
@ -54,10 +54,7 @@ const LoadScript = (
|
|||||||
const value = typeof v === "boolean"
|
const value = typeof v === "boolean"
|
||||||
? (v ? "" : null) // 布尔值处理为 HTML 标准属性格式
|
? (v ? "" : null) // 布尔值处理为 HTML 标准属性格式
|
||||||
: String(v); // 其他类型转为字符串
|
: String(v); // 其他类型转为字符串
|
||||||
|
if (value !== null) script.setAttribute(k, value);
|
||||||
if (value !== null) {
|
|
||||||
script.setAttribute(k, value);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
script.onload = () => resolve(script);
|
script.onload = () => resolve(script);
|
||||||
@ -86,7 +83,6 @@ const $GET = async (url: string, headers: Record<string, string> = {}): Promise<
|
|||||||
return res.json();
|
return res.json();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("GET request failed:", error);
|
console.error("GET request failed:", error);
|
||||||
throw error; // 抛出错误以便调用者处理
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -97,7 +93,6 @@ const $POST = async (url: string, data: Record<string, any>, headers: Record<str
|
|||||||
return res.json(); // 解析 JSON 数据
|
return res.json(); // 解析 JSON 数据
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("POST request failed:", error);
|
console.error("POST request failed:", error);
|
||||||
throw error; // 抛出错误以便调用者处理
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
type RouterEventKey = "beforeCreate" | "created" | "beforeMount" | "mounted" | "afterMount";
|
// 扩展 Window 接口以包含 swup 属性
|
||||||
type RouterEventName = "astro:before-preparation" | "astro:after-preparation" | "astro:before-swap" | "astro:after-swap" | "astro:page-load";
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
swup: { hooks: { on: (event: string, handler: EventHandler) => void } };
|
||||||
|
}
|
||||||
|
}
|
||||||
type EventHandler = (event: Event) => void;
|
type EventHandler = (event: Event) => void;
|
||||||
|
|
||||||
// 路由事件映射
|
// 进入页面时触发
|
||||||
const routerFn: Record<RouterEventKey, RouterEventName> = { beforeCreate: 'astro:before-preparation', created: 'astro:after-preparation', beforeMount: 'astro:before-swap', mounted: 'astro:after-swap', afterMount: 'astro:page-load' };
|
const inRouter = (handler: EventHandler) => {
|
||||||
|
const setup = () => window.swup.hooks.on("page:view", handler);
|
||||||
const updateRouter = (key: RouterEventKey, handler: EventHandler) => {
|
window.swup ? setup() : document.addEventListener("swup:enable", setup);
|
||||||
const eventName = routerFn[key];
|
|
||||||
if (!eventName) {
|
|
||||||
throw new Error(`Invalid key ${key} for router update. Valid keys are: ${Object.keys(routerFn).join(", ")}.`);
|
|
||||||
}
|
|
||||||
document.removeEventListener(eventName, handler);
|
|
||||||
document.addEventListener(eventName, handler);
|
|
||||||
};
|
};
|
||||||
|
// 离开当前页面时触发
|
||||||
|
const outRouter = (handler: EventHandler) => window.swup ? window.swup.hooks.on("visit:start", handler) : document.addEventListener("swup:enable", () => outRouter(handler));
|
||||||
|
|
||||||
export default updateRouter;
|
export { inRouter, outRouter };
|
||||||
Loading…
x
Reference in New Issue
Block a user