🍊
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
---
|
||||
import { fmtTime } from "../utils/index";
|
||||
const { articleList } = Astro.props;
|
||||
// 文章页面样式
|
||||
import "../styles/Archive.less";
|
||||
---
|
||||
|
||||
<section class="vh-animation vh-archive-main">
|
||||
<div class="archive-list">
|
||||
{
|
||||
articleList.map((i: any) => (
|
||||
<div class="archive-list-item">
|
||||
<p class="title">
|
||||
<em>{i.name}</em>
|
||||
<i />
|
||||
<span>{i.data.length} total</span>
|
||||
</p>
|
||||
{i.data.map((_: any) => (
|
||||
<a href={`/article/${_.id}`}>
|
||||
<em>{fmtTime(_.date, "MM-DD")}</em>
|
||||
<i />
|
||||
<span>{_.title}</span>
|
||||
<cite>{_.tags.map((tag: string) => `#${tag}`).join(" ")}</cite>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
import { Image } from "astro:assets";
|
||||
import { getDescription, fmtTime } from "../utils/index";
|
||||
const { post, index } = Astro.props;
|
||||
// 获取文章的摘要
|
||||
const description = getDescription(post);
|
||||
// 获取封面图
|
||||
import getCover from "../utils/getCover";
|
||||
const ARTICLE_COVER: string = await getCover(post.data.cover);
|
||||
// 文章Item组件样式
|
||||
import "../styles/components/ArticleCard.less";
|
||||
---
|
||||
|
||||
<article class="vh-article-item vh-animation" style={`animation-delay:calc(var(--vh-animation-delay) + ${index * 50}ms)`}>
|
||||
<a class="vh-article-link" href={`/article/${post.data.id}`}>
|
||||
<section class="vh-article-banner"><Image src={ARTICLE_COVER} alt={post.data.title} width="10" height="10" /></section>
|
||||
<section class="vh-article-desc">
|
||||
<header>
|
||||
<span class={`vh-article-categories vh-categories-${post.data.categories}`}>{post.data.categories}</span>
|
||||
<h2 class="title">{post.data.title}</h2>
|
||||
</header>
|
||||
<p class="vh-article-excerpt">{description}</p>
|
||||
<footer>{fmtTime(post.data.date)}</footer>
|
||||
</section>
|
||||
</a>
|
||||
</article>
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
// 获取用户配置数据
|
||||
import SITE_CONFIG from "../config";
|
||||
import { fmtTime, fmtDate } from "../utils/index";
|
||||
const { Avatar, Author, Motto, WebSites } = SITE_CONFIG;
|
||||
// 获取文章数据
|
||||
import { getCategories, getTags, getRecommendArticles } from "../utils/getPostInfo";
|
||||
// 分类列表
|
||||
const categories = getCategories();
|
||||
// 热门标签
|
||||
const tags = getTags();
|
||||
// 最新文章
|
||||
const recommendArticles = getRecommendArticles();
|
||||
// 静态图片
|
||||
import { Image } from "astro:assets";
|
||||
// 侧边栏样式
|
||||
import "../styles/components/Aside.less";
|
||||
---
|
||||
|
||||
<aside class="vh-aside">
|
||||
<!-- 头像块 -->
|
||||
<section class="vh-aside-item user">
|
||||
<div onclick="window.history.back()" class="vh-aside-avatar">
|
||||
<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>
|
||||
<p class="vh-aside-motto">{Motto}</p>
|
||||
<section class="vh-aside-links">
|
||||
{
|
||||
WebSites.map(item => (
|
||||
<a class="vh-aside-links-item" href={item.link} title={item.text} target="_blank" rel="noopener nofollow">
|
||||
<img src={item.icon} />
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- 分类块 -->
|
||||
<section class="vh-aside-item categories">
|
||||
<h3>分类</h3>
|
||||
<div class="vh-aside-categories">
|
||||
{
|
||||
categories.map(i => (
|
||||
<a href={`/categories/${i.title}`}>
|
||||
<span>{i.title}</span>
|
||||
<i>{i.count}</i>
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 标签块 -->
|
||||
<section class="vh-aside-item tags">
|
||||
<h3>热门标签</h3>
|
||||
<div class="vh-aside-tags">
|
||||
{
|
||||
tags.map(i => (
|
||||
<a href={`/tags/${i}`}>
|
||||
<span>{i}</span>
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 最新文章块 -->
|
||||
<section class="vh-aside-item articles">
|
||||
<h3>推荐文章</h3>
|
||||
<div class="vh-aside-articles">
|
||||
{
|
||||
recommendArticles.map(async i => (
|
||||
<a href={`/article/${(await i).id}`}>
|
||||
<p class="cover">
|
||||
<img src={(await i).cover} />
|
||||
</p>
|
||||
<p class="info">
|
||||
<span>{(await i).title}</span>
|
||||
<time>{fmtDate((await i).date)}</time>
|
||||
</p>
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
</aside>
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
import "../styles/components/BackTop.less";
|
||||
---
|
||||
|
||||
<section class="vh-back-top">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<defs>
|
||||
<linearGradient id="Gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:blue;stop-opacity:1"></stop>
|
||||
<stop offset="50%" style="stop-color:violet;stop-opacity:1"></stop>
|
||||
<stop offset="100%" style="stop-color:indigo;stop-opacity:1"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<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 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>
|
||||
<script>
|
||||
import updateRouter from "../utils/updateRouter";
|
||||
import BackTopInitFn from "../scripts/BackTop";
|
||||
// 初始化BackTop组件
|
||||
updateRouter("afterMount", BackTopInitFn);
|
||||
</script>
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
import "../styles/components/Comment.less";
|
||||
---
|
||||
|
||||
<section class="vh-comment"><section></section></section>
|
||||
|
||||
<script>
|
||||
import updateRouter from "../utils/updateRouter";
|
||||
import { LoadScript } from "../utils/index";
|
||||
import SITE_CONFIG from "../config";
|
||||
const { Twikoo } = SITE_CONFIG;
|
||||
declare const twikoo: any;
|
||||
// 初始化评论插件
|
||||
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: Twikoo.envId,
|
||||
el: ".vh-comment>section"
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
const { time, sitename, title, site, id, auther } = Astro.props;
|
||||
const articleURL = `${site}/article/${id}`;
|
||||
import "../styles/components/Copyright.less";
|
||||
---
|
||||
|
||||
<section class="vh-copyright">
|
||||
<p>本文由 <span>{auther}</span> 于 {time} 发布</p>
|
||||
<p>文章地址:<a href={articleURL}>{title}</a></p>
|
||||
<p>本博客所有文章除特别声明外,均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0" target="_blank" rel="noopener nofollow">CC BY-NC-SA 4.0</a> 许可协议。完整转载请注明来自 <a href={site}>{sitename}</a>!</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"> <path d="M245.8 214.9l-33.2 17.3c-9.4-19.6-25.2-19.9-27.5-19.9-22.1 0-33.2 14.6-33.2 43.8 0 23.6 9.2 43.8 33.2 43.8 14.5 0 24.7-7.1 30.6-21.3l30.6 15.5c-6.2 11.5-25.7 39-65.1 39-22.6 0-74-10.3-74-77.1 0-58.7 43-77.1 72.6-77.1 30.7 0 52.7 12 66 35.9zm143.1 0l-32.8 17.3c-9.5-19.8-25.7-19.9-27.9-19.9-22.1 0-33.2 14.6-33.2 43.8 0 23.6 9.2 43.8 33.2 43.8 14.5 0 24.7-7.1 30.5-21.3l31 15.5c-2.1 3.8-21.4 39-65.1 39-22.7 0-74-9.9-74-77.1 0-58.7 43-77.1 72.6-77.1 30.7 0 52.6 12 65.6 35.9zM247.6 8.1C104.7 8.1 0 123.1 0 256.1c0 138.5 113.6 248 247.6 248 129.9 0 248.4-100.9 248.4-248 0-137.9-106.6-248-248.4-248zm.9 450.8c-112.5 0-203.7-93-203.7-202.8 0-105.4 85.4-203.3 203.7-203.3 112.5 0 202.8 89.5 202.8 203.3 0 121.7-99.7 202.8-202.8 202.8z"></path></svg>
|
||||
</section>
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
import SITE_CONFIG from "../config";
|
||||
// 当前页面的 URL 元地址
|
||||
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
|
||||
// 页面内容的元数据
|
||||
const { Title: title, Keywords, Description } = Astro.props;
|
||||
const { Site, Title, Subtitle, Author, Cover, DNSOptimization } = SITE_CONFIG;
|
||||
// 基础 样式
|
||||
import "../styles/Base.less";
|
||||
---
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no" />
|
||||
<title>{title ? `${title} | ${Title}` : `${Title} - ${Subtitle}`}</title>
|
||||
<meta name="keywords" content={(Keywords || ["Astro", "分享", "博客"]).join(", ")} />
|
||||
<meta name="description" content={Description} />
|
||||
<meta name="format-detection" content="telephone=no, email=no, date=no, address=no" />
|
||||
<!-- 元数据 -->
|
||||
<meta name="title" content={Title} />
|
||||
<meta name="site" content={Site} />
|
||||
<meta name="author" content={Author} />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<meta property="article:author" content={Author} />
|
||||
<meta name="robots" content="max-image-preview:large" />
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content={Title} />
|
||||
<meta property="og:url" content={canonicalURL} />
|
||||
<meta property="og:site_name" content={Title} />
|
||||
<meta property="og:description" content={Description} />
|
||||
<meta property="og:locale" content="zh_CN" />
|
||||
<meta property="og:image" content={Cover} />
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content={canonicalURL} />
|
||||
<meta property="twitter:title" content={Title} />
|
||||
<meta property="twitter:description" content={Description} />
|
||||
<meta property="twitter:image" content={Cover} />
|
||||
<!-- Sitemap -->
|
||||
<link rel="sitemap" href="/sitemap-index.xml" />
|
||||
<!-- DNS预解析 -->
|
||||
{DNSOptimization.map((url: string) => <link rel="dns-prefetch" href={url} />)}
|
||||
{DNSOptimization.map((url: string) => <link rel="preconnect" href={url} />)}
|
||||
<!-- LINK 标签 -->
|
||||
<link rel="canonical" href={canonicalURL} />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<slot />
|
||||
</head>
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
// 导航高亮
|
||||
const { activeNav } = Astro.props;
|
||||
import SITE_CONFIG from "../config";
|
||||
const { Navs } = SITE_CONFIG;
|
||||
// 搜索框组件
|
||||
import Search from "./Search.astro";
|
||||
// 顶部 Header 样式
|
||||
import "../styles/components/Header.less";
|
||||
---
|
||||
|
||||
<header class="vh-header">
|
||||
<section class="main">
|
||||
<a href="/" class="index">
|
||||
<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
|
||||
</a>
|
||||
<div class="link-list">
|
||||
{
|
||||
Navs.map(i => (
|
||||
<a class={i.link.includes(activeNav) ? "active" : ""} href={i.link} target={i.target ? "_blank" : "_self"}>
|
||||
{i.text}
|
||||
<span set:html={i.icon} />
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<div class="nav-btn">
|
||||
<span class="nav-btn-item search-btn"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"></path></svg> 搜索</span>
|
||||
<span class="nav-btn-item menu-btn"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M0 96C0 78.3 14.3 64 32 64l384 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 128C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32l384 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 288c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32L32 448c-17.7 0-32-14.3-32-32s14.3-32 32-32l384 0c17.7 0 32 14.3 32 32z"></path></svg></span>
|
||||
</div>
|
||||
<Search />
|
||||
</section>
|
||||
</header>
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
// 导航高亮
|
||||
const { activeNav } = Astro.props;
|
||||
import SITE_CONFIG from "../config";
|
||||
const { Navs, Title } = SITE_CONFIG;
|
||||
// 侧边栏 MobileSidebar 样式
|
||||
import "../styles/components/MobileSidebar.less";
|
||||
---
|
||||
|
||||
<nav class="vh-mobilesidebar">
|
||||
<section class="main">
|
||||
<div class="vh-mobilesidebar-list user-panel">
|
||||
<h3>{Title}</h3>
|
||||
</div>
|
||||
|
||||
<div class="vh-mobilesidebar-list">
|
||||
{
|
||||
Navs.map(i => (
|
||||
<a class={i.link.includes(activeNav) ? "active" : ""} href={i.link} target={i.target ? "_blank" : "_self"}>
|
||||
<span set:html={i.icon} />
|
||||
{i.text}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
</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>
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
const { data } = Astro.props;
|
||||
// 处理页码
|
||||
import { fmtPage } from "../utils/index";
|
||||
// 翻页组件样式
|
||||
import "../styles/components/Pagination.less";
|
||||
---
|
||||
|
||||
<section class="vh-article-pagination">
|
||||
<!-- 上一页 -->
|
||||
<a class={`vh-pagination-item${!data.prev ? " disabled" : ""}`} href={data.prev}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
|
||||
<path d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256 246.6 86.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<!-- 首页 -->
|
||||
{
|
||||
data.first && data.prev && (
|
||||
<a class={`vh-pagination-item`} href={data.first}>
|
||||
1
|
||||
</a>
|
||||
)
|
||||
}
|
||||
<!-- 总页的前一页 -->
|
||||
{
|
||||
(!data.next || !data.last) && (
|
||||
<a class={`vh-pagination-item`} href={data.prev}>
|
||||
{fmtPage(data.prev)}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
<!-- 当前页 -->
|
||||
<a class="vh-pagination-item active">{fmtPage(data.current) || "1"}</a>
|
||||
<!-- 第一页的后一页 -->
|
||||
{
|
||||
(!data.prev || !data.first) && (
|
||||
<a class={`vh-pagination-item`} href={data.next}>
|
||||
{fmtPage(data.next)}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
<!-- 总页 -->
|
||||
{
|
||||
data.next && data.last && (
|
||||
<a class={`vh-pagination-item`} href={data.last}>
|
||||
{fmtPage(data.last)}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
<!-- 下一页 -->
|
||||
<a class={`vh-pagination-item${!data.next ? " disabled" : ""}`} href={data.next}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
|
||||
<path d="M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L242.7 256 73.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</section>
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
// 搜索样式
|
||||
import "../styles/components/Search.less";
|
||||
---
|
||||
|
||||
<section class="vh-search">
|
||||
<main>
|
||||
<div class="search-input">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#B4B4B4" d="m19.6 21l-6.3-6.3q-.75.6-1.725.95T9.5 16q-2.725 0-4.612-1.888T3 9.5t1.888-4.612T9.5 3t4.613 1.888T16 9.5q0 1.1-.35 2.075T14.7 13.3l6.3 6.3zM9.5 14q1.875 0 3.188-1.312T14 9.5t-1.312-3.187T9.5 5T6.313 6.313T5 9.5t1.313 3.188T9.5 14"></path></svg>
|
||||
<input type="text" placeholder="Search" />
|
||||
</div>
|
||||
<section class="vh-search-list"></section>
|
||||
</main>
|
||||
</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>
|
||||
@@ -0,0 +1,44 @@
|
||||
export default {
|
||||
Title: '韩小韩博客',
|
||||
Site: 'https://www.vvhan.com',
|
||||
Subtitle: '不曾与你分享的时间,我在进步.',
|
||||
Description: '韩小韩博客 专注于前开发与相关技术的实战分享,涵盖Vue框架、Node.js、Serverless等,并涉及Node、Python、Linux、Docker等领域。同时,博客也分享作者的生活、音乐和旅行的热爱。',
|
||||
Author: '.𝙃𝙖𝙣',
|
||||
Motto: '运气是计划之外的东西.',
|
||||
Avatar: 'https://q1.qlogo.cn/g?b=qq&nk=1655466387&s=640',
|
||||
// 导航栏 (新窗口打开 newWindow: true)
|
||||
Navs: [
|
||||
{ text: '朋友', link: '/links', icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M364.2 83.8c-24.4-24.4-64-24.4-88.4 0l-184 184c-42.1 42.1-42.1 110.3 0 152.4s110.3 42.1 152.4 0l152-152c10.9-10.9 28.7-10.9 39.6 0s10.9 28.7 0 39.6l-152 152c-64 64-167.6 64-231.6 0s-64-167.6 0-231.6l184-184c46.3-46.3 121.3-46.3 167.6 0s46.3 121.3 0 167.6l-176 176c-28.6 28.6-75 28.6-103.6 0s-28.6-75 0-103.6l144-144c10.9-10.9 28.7-10.9 39.6 0s10.9 28.7 0 39.6l-144 144c-6.7 6.7-6.7 17.7 0 24.4s17.7 6.7 24.4 0l176-176c24.4-24.4 24.4-64 0-88.4z"/></svg>' },
|
||||
{ text: '动态', link: '/talking', icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M500.3 7.3C507.7 13.3 512 22.4 512 32l0 144c0 26.5-28.7 48-64 48s-64-21.5-64-48s28.7-48 64-48l0-57L352 90.2 352 208c0 26.5-28.7 48-64 48s-64-21.5-64-48s28.7-48 64-48l0-96c0-15.3 10.8-28.4 25.7-31.4l160-32c9.4-1.9 19.1 .6 26.6 6.6zM74.7 304l11.8-17.8c5.9-8.9 15.9-14.2 26.6-14.2l61.7 0c10.7 0 20.7 5.3 26.6 14.2L213.3 304l26.7 0c26.5 0 48 21.5 48 48l0 112c0 26.5-21.5 48-48 48L48 512c-26.5 0-48-21.5-48-48L0 352c0-26.5 21.5-48 48-48l26.7 0zM192 408a48 48 0 1 0 -96 0 48 48 0 1 0 96 0zM478.7 278.3L440.3 368l55.7 0c6.7 0 12.6 4.1 15 10.4s.6 13.3-4.4 17.7l-128 112c-5.6 4.9-13.9 5.3-19.9 .9s-8.2-12.4-5.3-19.2L391.7 400 336 400c-6.7 0-12.6-4.1-15-10.4s-.6-13.3 4.4-17.7l128-112c5.6-4.9 13.9-5.3 19.9-.9s8.2 12.4 5.3 19.2zm-339-59.2c-6.5 6.5-17 6.5-23 0L19.9 119.2c-28-29-26.5-76.9 5-103.9c27-23.5 68.4-19 93.4 6.5l10 10.5 9.5-10.5c25-25.5 65.9-30 93.9-6.5c31 27 32.5 74.9 4.5 103.9l-96.4 99.9z"/></svg>' },
|
||||
{ text: '昔日', link: '/archives', icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M448 96c0-35.3-28.7-64-64-64L64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l320 0c35.3 0 64-28.7 64-64l0-320zM256 160c0 17.7-14.3 32-32 32l-96 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l96 0c17.7 0 32 14.3 32 32zm64 64c17.7 0 32 14.3 32 32s-14.3 32-32 32l-192 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l192 0zM192 352c0 17.7-14.3 32-32 32l-32 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l32 0c17.7 0 32 14.3 32 32z"/></svg>' },
|
||||
{ text: '留言', link: '/message', icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M64 0C28.7 0 0 28.7 0 64L0 352c0 35.3 28.7 64 64 64l96 0 0 80c0 6.1 3.4 11.6 8.8 14.3s11.9 2.1 16.8-1.5L309.3 416 448 416c35.3 0 64-28.7 64-64l0-288c0-35.3-28.7-64-64-64L64 0z"/></svg>' },
|
||||
{ text: '关于', link: '/about', icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l448 0c35.3 0 64-28.7 64-64l0-320c0-35.3-28.7-64-64-64L64 32zm80 256l64 0c44.2 0 80 35.8 80 80c0 8.8-7.2 16-16 16L80 384c-8.8 0-16-7.2-16-16c0-44.2 35.8-80 80-80zm-32-96a64 64 0 1 1 128 0 64 64 0 1 1 -128 0zm256-32l128 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-128 0c-8.8 0-16-7.2-16-16s7.2-16 16-16zm0 64l128 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-128 0c-8.8 0-16-7.2-16-16s7.2-16 16-16zm0 64l128 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-128 0c-8.8 0-16-7.2-16-16s7.2-16 16-16z"/></svg>' },
|
||||
{ text: 'API', link: 'https://api.vvhan.com/', target: true, icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#D2D2D2" d="M352 0c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9L370.7 96 201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L416 141.3l41.4 41.4c9.2 9.2 22.9 11.9 34.9 6.9s19.8-16.6 19.8-29.6l0-128c0-17.7-14.3-32-32-32L352 0zM80 32C35.8 32 0 67.8 0 112L0 432c0 44.2 35.8 80 80 80l320 0c44.2 0 80-35.8 80-80l0-112c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 112c0 8.8-7.2 16-16 16L80 448c-8.8 0-16-7.2-16-16l0-320c0-8.8 7.2-16 16-16l112 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L80 32z" /></svg>' },
|
||||
],
|
||||
// 侧边栏个人网站
|
||||
WebSites: [
|
||||
{ text: 'Github', link: 'https://github.com/uxiaohan', icon: '/assets/images/svg/github.svg' },
|
||||
{ text: '韩小韩API', link: 'https://api.vvhan.com', icon: '/assets/images/svg/api.svg' },
|
||||
{ text: '每日热榜', link: 'https://hot.vvhan.com', icon: '/assets/images/svg/hot.svg' },
|
||||
{ text: '骤雨重山图床', link: 'https://wp-cdn.4ce.cn', icon: '/assets/images/svg/img.svg' },
|
||||
{ text: 'HanAnalytics', link: 'https://analytics.vvhan.com', icon: '/assets/images/svg/analytics.svg' },
|
||||
],
|
||||
// Cover 使用绝对路径
|
||||
Cover: '/assets/images/banner/072c12ec85d2d3b5.webp',
|
||||
CreateTime: '2021-09-01',
|
||||
// DNS预解析地址
|
||||
DNSOptimization: [
|
||||
'https://i0.wp.com',
|
||||
'https://tools-api.vvhan.com',
|
||||
'https://pagead2.googlesyndication.com',
|
||||
'https://lf9-cdn-tos.bytecdntp.com',
|
||||
'https://registry.npmmirror.com',
|
||||
'https://npm.elemecdn.com'
|
||||
],
|
||||
// 博客音乐组件解析接口
|
||||
vhMusicApi: 'https://meting-dd.2333332.xyz/api',
|
||||
// 评论组件 Twikoo
|
||||
Twikoo: {
|
||||
envId: "https://twikoo.vvhan.com/.netlify/functions/twikoo",
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { glob } from 'astro/loaders';
|
||||
import { defineCollection, z } from 'astro:content';
|
||||
|
||||
const blog = defineCollection({
|
||||
// Load Markdown and MDX files in the `src/content/blog/` directory.
|
||||
loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }),
|
||||
// Type-check frontmatter using a schema
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
date: z.coerce.date(),
|
||||
updated: z.coerce.date().optional(),
|
||||
categories: z.string(),
|
||||
tags: z.array(z.union([z.string(), z.number()])),
|
||||
id: z.union([z.string(), z.number()]),
|
||||
cover: z.string().optional(),
|
||||
recommend: z.boolean().optional()
|
||||
}),
|
||||
});
|
||||
|
||||
export const collections = { blog };
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
import { ClientRouter } from "astro:transitions";
|
||||
const { title, keywords, description, activeNav } = Astro.props;
|
||||
// Head 依赖
|
||||
import Head from "../components/Head.astro";
|
||||
// 顶部 Header
|
||||
import Header from "../components/Header.astro";
|
||||
// 返回顶部
|
||||
import BackTop from "../components/BackTop.astro";
|
||||
// 手机端侧边栏
|
||||
import MobileSidebar from "../components/MobileSidebar.astro";
|
||||
// Layout 样式
|
||||
import "../styles/Layout.less";
|
||||
---
|
||||
|
||||
<html lang="zh-CN">
|
||||
<Head Title={title} Keywords={keywords} Description={description}>
|
||||
<ClientRouter />
|
||||
</Head>
|
||||
|
||||
<body>
|
||||
<MobileSidebar activeNav={activeNav} />
|
||||
<Header activeNav={activeNav} />
|
||||
<main class="vh-main">
|
||||
<slot />
|
||||
</main>
|
||||
<BackTop />
|
||||
<script>
|
||||
import updateRouter from "../utils/updateRouter";
|
||||
// 搜索
|
||||
import { searchFn } from "../scripts/Search";
|
||||
// 图片懒加载
|
||||
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 });
|
||||
// 初始化 图片懒加载初
|
||||
updateRouter("afterMount", () => {
|
||||
// 预加载搜索数据
|
||||
searchFn("");
|
||||
// 图片懒加载初始化
|
||||
vhLzImgInit();
|
||||
// 进入页面,进度条结束
|
||||
NProgress.done();
|
||||
});
|
||||
// 离开页面,进度条开始
|
||||
updateRouter("beforeCreate", NProgress.start);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
import { getCollection } from "astro:content";
|
||||
import type { GetStaticPathsOptions } from "astro";
|
||||
import setSearchJson from "../utils/vhSearch";
|
||||
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 文件 ======
|
||||
await setSearchJson(posts);
|
||||
// 生成 Search JSON 文件 ======
|
||||
return paginate(posts, { pageSize: 15 });
|
||||
}
|
||||
|
||||
const { page } = Astro.props;
|
||||
const { data, ...page_data } = page;
|
||||
// 页面 Info
|
||||
import SITE_CONFIG from "../config";
|
||||
const { Description } = SITE_CONFIG;
|
||||
// 博客列表组件
|
||||
import ArticleCard from "../components/ArticleCard.astro";
|
||||
// 公共 Layout
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
// Aside组件
|
||||
import Aside from "../components/Aside.astro";
|
||||
// 分页组件
|
||||
import Pagination from "../components/Pagination.astro";
|
||||
---
|
||||
|
||||
<Layout title="" description={Description}>
|
||||
<section class="vh-container">
|
||||
<section class="vh-animation article-list">
|
||||
<!-- 文章列表 -->
|
||||
{data.map((post, index) => <ArticleCard post={post} index={index} />)}
|
||||
<!-- 分页 -->
|
||||
<Pagination data={page_data.url} />
|
||||
</section>
|
||||
<Aside />
|
||||
</section>
|
||||
</Layout>
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
// 页面 Info
|
||||
import SITE_CONFIG from "../../config";
|
||||
const { Description } = SITE_CONFIG;
|
||||
// Aside组件
|
||||
import Aside from "../../components/Aside.astro";
|
||||
// 公共 Layout
|
||||
import Layout from "../../layouts/Layout.astro";
|
||||
// 评论组件
|
||||
import Comment from "../../components/Comment.astro";
|
||||
// 动态说说样式
|
||||
import "../../styles/About.less";
|
||||
---
|
||||
|
||||
<Layout title="关于" description={Description} activeNav="about">
|
||||
<section class="vh-container">
|
||||
<section class="vh-about">
|
||||
<header class="vh-page-header">
|
||||
<h1>关于我</h1>
|
||||
<p>Hi there, I’m Han 👋</p>
|
||||
</header>
|
||||
<main>
|
||||
<p>我是一名热爱前端开发并拥有丰富经验的工程师,喜欢探索新技术并应用于实际项目中。 我始终保持对新技术的热情,并将我的知识与经验分享在我的博客中。我期待在这里与你分享我的见解、经验和最新的技术动态。</p>
|
||||
<h3>🌟 Languages and Tools</h3>
|
||||
<div class="language-tool">
|
||||
<a href="https://www.w3.org/html/" target="_blank" rel="noopener nofollow"><img data-vh-lz-src="https://img.shields.io/badge/HTML5-E34F26?style=for-the-badge&logo=html5&logoColor=white" alt="HTML5" /></a>
|
||||
<a href="https://nodejs.org" target="_blank" rel="noopener nofollow"><img data-vh-lz-src="https://img.shields.io/badge/Node.js-339933?style=for-the-badge&logo=node.js&logoColor=white" alt="Node.js" /></a>
|
||||
<a href="https://www.php.net/" target="_blank" rel="noopener nofollow"><img data-vh-lz-src="https://img.shields.io/badge/php-3776AB?style=for-the-badge&logo=php&logoColor=white" alt="PHP" /></a>
|
||||
<a href="https://www.python.org" target="_blank" rel="noopener nofollow"><img data-vh-lz-src="https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=white" alt="Python" /></a>
|
||||
<a href="https://git-scm.com/" target="_blank" rel="noopener nofollow"><img data-vh-lz-src="https://img.shields.io/badge/Git-F05032?style=for-the-badge&logo=git&logoColor=white" alt="Git" /> </a>
|
||||
<a href="https://www.linux.org/" target="_blank" rel="noopener nofollow"><img data-vh-lz-src="https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black" alt="Linux" /></a>
|
||||
<a href="https://www.nginx.com" target="_blank" rel="noopener nofollow"><img data-vh-lz-src="https://img.shields.io/badge/Nginx-009639?style=for-the-badge&logo=nginx&logoColor=white" alt="Nginx" /></a>
|
||||
<a href="https://www.docker.com/" target="_blank" rel="noopener nofollow"><img data-vh-lz-src="https://img.shields.io/badge/Docker-2496ED?style=for-the-badge&logo=docker&logoColor=white" alt="Docker" /></a>
|
||||
<a href="https://vuejs.org/" target="_blank" rel="noopener nofollow"><img data-vh-lz-src="https://img.shields.io/badge/Vue.js-4FC08D?style=for-the-badge&logo=vue.js&logoColor=white" alt="Vue.js" /></a>
|
||||
<a href="https://vitejs.dev/" target="_blank" rel="noopener nofollow"><img data-vh-lz-src="https://img.shields.io/badge/Vite-646CFF?style=for-the-badge&logo=vite&logoColor=white" alt="Vite" /></a>
|
||||
<a href="https://webpack.js.org" target="_blank" rel="noopener nofollow"><img data-vh-lz-src="https://img.shields.io/badge/Webpack-8DD6F9?style=for-the-badge&logo=webpack&logoColor=black" alt="Webpack" /></a>
|
||||
<a href="https://www.electronjs.org" target="_blank" rel="noopener nofollow"><img data-vh-lz-src="https://img.shields.io/badge/Electron-47848F?style=for-the-badge&logo=electron&logoColor=white" alt="Electron" /></a>
|
||||
<a href="https://expressjs.com" target="_blank" rel="noopener nofollow"><img data-vh-lz-src="https://img.shields.io/badge/Express-000000?style=for-the-badge&logo=express&logoColor=white" alt="Express" /></a>
|
||||
<a href="https://tailwindcss.com/" target="_blank" rel="noopener nofollow"><img data-vh-lz-src="https://img.shields.io/badge/Tailwind_CSS-38B2AC?style=for-the-badge&logo=tailwind-css&logoColor=white" alt="Tailwind CSS" /></a>
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript" target="_blank" rel="noopener nofollow"><img data-vh-lz-src="https://img.shields.io/badge/JavaScript-F7DF1E?style=for-the-badge&logo=javascript&logoColor=black" alt="JavaScript" /></a>
|
||||
<a href="https://www.sqlite.org/" target="_blank" rel="noopener nofollow"><img data-vh-lz-src="https://img.shields.io/badge/SQLite-003B57?style=for-the-badge&logo=sqlite&logoColor=white" alt="SQLite" /></a>
|
||||
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener nofollow"><img data-vh-lz-src="https://img.shields.io/badge/VS_Code-007ACC?style=for-the-badge&logo=visual-studio-code&logoColor=white" alt="VS Code" /></a>
|
||||
</div>
|
||||
</main>
|
||||
<Comment />
|
||||
</section>
|
||||
<Aside />
|
||||
</section>
|
||||
</Layout>
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
import { getArchiveList } from "../../utils/getArchive";
|
||||
// 获取归档列表
|
||||
const articleList = await getArchiveList();
|
||||
// 页面 Info
|
||||
import SITE_CONFIG from "../../config";
|
||||
const { Description } = SITE_CONFIG;
|
||||
// 公共 Layout
|
||||
import Layout from "../../layouts/Layout.astro";
|
||||
// Aside组件
|
||||
import Aside from "../../components/Aside.astro";
|
||||
// 文章列表组件
|
||||
import Archive from "../../components/Archive.astro";
|
||||
---
|
||||
|
||||
<Layout title="归档" description={Description} activeNav="archives">
|
||||
<section class="vh-container">
|
||||
<Archive articleList={articleList} />
|
||||
<Aside />
|
||||
</section>
|
||||
</Layout>
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
import { getDescription, fmtTime } from "../../utils/index";
|
||||
import { type CollectionEntry, getCollection } from "astro:content";
|
||||
import { render } from "astro:content";
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getCollection("blog");
|
||||
return posts.map((post: any) => ({ params: { article: post.data.id }, props: post }));
|
||||
}
|
||||
// 处理文章内容
|
||||
type Props = CollectionEntry<"blog">;
|
||||
const post: any = Astro.props;
|
||||
// 页面 Info
|
||||
import SITE_CONFIG from "../../config";
|
||||
// 处理文章内容
|
||||
const description = getDescription(post);
|
||||
const { Content } = await render(post);
|
||||
// 公共 Layout
|
||||
import Layout from "../../layouts/Layout.astro";
|
||||
// Aside组件
|
||||
import Aside from "../../components/Aside.astro";
|
||||
// Footer 组件
|
||||
import Copyright from "../../components/Copyright.astro";
|
||||
// 评论组件
|
||||
import Comment from "../../components/Comment.astro";
|
||||
// 文章页面样式
|
||||
import "../../styles/Article.less";
|
||||
---
|
||||
|
||||
<Layout title={post.data.title} keywords={post.data.tags} description={description}>
|
||||
<section class="vh-container">
|
||||
<article class="vh-animation vh-article-main">
|
||||
<header>
|
||||
<h1>{post.data.title}</h1>
|
||||
<div class="article-meta">
|
||||
<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>
|
||||
<time>{fmtTime(post.data.date, "YYYY-MM-DD")}</time>
|
||||
</span>
|
||||
<span class="article-meta-item">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <path d="M75 75L41 41C25.9 25.9 0 36.6 0 57.9L0 168c0 13.3 10.7 24 24 24l110.1 0c21.4 0 32.1-25.9 17-41l-30.8-30.8C155 85.5 203 64 256 64c106 0 192 86 192 192s-86 192-192 192c-40.8 0-78.6-12.7-109.7-34.4c-14.5-10.1-34.4-6.6-44.6 7.9s-6.6 34.4 7.9 44.6C151.2 495 201.7 512 256 512c141.4 0 256-114.6 256-256S397.4 0 256 0C185.3 0 121.3 28.7 75 75zm181 53c-13.3 0-24 10.7-24 24l0 104c0 6.4 2.5 12.5 7 17l72 72c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-65-65 0-94.1c0-13.3-10.7-24-24-24z"></path></svg>
|
||||
<time>{fmtTime(post.data.updated, "YYYY-MM-DD")}</time>
|
||||
</span>
|
||||
<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>
|
||||
<span>{post.data.categories}</span>
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
<Content />
|
||||
<Copyright site={SITE_CONFIG.Site} id={post.data.id} title={post.data.title} sitename={SITE_CONFIG.Title} time={fmtTime(post.data.date, "YYYY-MM-DD")} auther={SITE_CONFIG.Author} />
|
||||
<Comment />
|
||||
</article>
|
||||
<Aside />
|
||||
</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>
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
import { getCategoriesList } from "../../utils/getArchive";
|
||||
import { getCollection } from "astro:content";
|
||||
export async function getStaticPaths(): Promise<any> {
|
||||
const posts = await getCollection("blog");
|
||||
return posts.map((post: any) => ({ params: { categories: post.data.categories }, props: post }));
|
||||
}
|
||||
// 获取分类列表
|
||||
const { categories } = Astro.params;
|
||||
const articleList = await getCategoriesList(categories);
|
||||
// 页面 Info
|
||||
import SITE_CONFIG from "../../config";
|
||||
const { Description } = SITE_CONFIG;
|
||||
// 公共 Layout
|
||||
import Layout from "../../layouts/Layout.astro";
|
||||
// Aside组件
|
||||
import Aside from "../../components/Aside.astro";
|
||||
// 文章列表组件
|
||||
import Archive from "../../components/Archive.astro";
|
||||
---
|
||||
|
||||
<Layout title={`分类 ${categories}`} description={Description}>
|
||||
<section class="vh-container">
|
||||
<Archive articleList={articleList} />
|
||||
<Aside />
|
||||
</section>
|
||||
</Layout>
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
// 页面 Info
|
||||
import SITE_CONFIG from "../../config";
|
||||
const { Description } = SITE_CONFIG;
|
||||
// Aside组件
|
||||
import Aside from "../../components/Aside.astro";
|
||||
// 公共 Layout
|
||||
import Layout from "../../layouts/Layout.astro";
|
||||
// 评论组件
|
||||
import Comment from "../../components/Comment.astro";
|
||||
// 动态说说样式
|
||||
import "../../styles/Links.less";
|
||||
---
|
||||
|
||||
<Layout title="友情链接" description={Description} activeNav="links">
|
||||
<section class="vh-container">
|
||||
<section class="vh-links">
|
||||
<header class="vh-page-header">
|
||||
<h1>朋友圈 👭</h1>
|
||||
<p>天下快意之事莫若友。</p>
|
||||
</header>
|
||||
<main></main>
|
||||
<Comment />
|
||||
</section>
|
||||
<Aside />
|
||||
</section>
|
||||
|
||||
<script>
|
||||
import updateRouter from "../../utils/updateRouter";
|
||||
import LinksInit from "../../scripts/Links";
|
||||
// 进入页面时初始化
|
||||
updateRouter("afterMount", LinksInit);
|
||||
</script>
|
||||
</Layout>
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
// 页面 Info
|
||||
import SITE_CONFIG from "../../config";
|
||||
const { Description } = SITE_CONFIG;
|
||||
// Aside组件
|
||||
import Aside from "../../components/Aside.astro";
|
||||
// 公共 Layout
|
||||
import Layout from "../../layouts/Layout.astro";
|
||||
// 评论组件
|
||||
import Comment from "../../components/Comment.astro";
|
||||
---
|
||||
|
||||
<Layout title="留言" description={Description} activeNav="message">
|
||||
<section class="vh-container">
|
||||
<section class="vh-message" style="gap:1.25rem;">
|
||||
<header class="vh-page-header">
|
||||
<h1>留言板 🌸</h1>
|
||||
<p>快友之事莫若谈。</p>
|
||||
</header>
|
||||
<Comment />
|
||||
</section>
|
||||
<Aside />
|
||||
</section>
|
||||
|
||||
<script>
|
||||
import updateRouter from "../../utils/updateRouter";
|
||||
import LinksInit from "../../scripts/Links";
|
||||
// 进入页面时初始化
|
||||
updateRouter("afterMount", LinksInit);
|
||||
</script>
|
||||
</Layout>
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
|
||||
const getRobotsTxt = (sitemapURL: URL) => `User-agent: *
|
||||
Disallow: /MineWeb/
|
||||
Disallow: /assets/
|
||||
Disallow: /*?*
|
||||
|
||||
Sitemap: ${sitemapURL.href}`;
|
||||
|
||||
export const GET: APIRoute = ({ site }) => {
|
||||
const sitemapURL = new URL('sitemap-index.xml', site);
|
||||
return new Response(getRobotsTxt(sitemapURL));
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import rss from '@astrojs/rss';
|
||||
import { getCollection } from 'astro:content';
|
||||
import { getDescription } from '../utils/index'
|
||||
import SITE_CONFIG from '../config';
|
||||
const { Title, Description } = SITE_CONFIG;
|
||||
|
||||
|
||||
export async function GET(context: any) {
|
||||
const posts = await getCollection('blog');
|
||||
return rss({
|
||||
title: Title,
|
||||
description: Description,
|
||||
site: context.site,
|
||||
items: posts.map((post) => ({
|
||||
title: post.data.title,
|
||||
pubDate: post.data.updated || post.data.date,
|
||||
description: getDescription(post),
|
||||
link: `/article/${post.data.id}`
|
||||
})),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
import { getTagsList } from "../../utils/getArchive";
|
||||
import { getCollection } from "astro:content";
|
||||
export async function getStaticPaths(): Promise<any> {
|
||||
const posts = await getCollection("blog");
|
||||
let tagList: any = [];
|
||||
posts.forEach(post => (tagList = [...tagList, ...post.data.tags]));
|
||||
return [...new Set(tagList)].map(tags => ({ params: { tags } }));
|
||||
}
|
||||
// 获取分类列表
|
||||
const { tags } = Astro.params;
|
||||
const articleList = await getTagsList(tags);
|
||||
// 页面 Info
|
||||
import SITE_CONFIG from "../../config";
|
||||
const { Description } = SITE_CONFIG;
|
||||
// 公共 Layout
|
||||
import Layout from "../../layouts/Layout.astro";
|
||||
// Aside组件
|
||||
import Aside from "../../components/Aside.astro";
|
||||
// 文章列表组件
|
||||
import Archive from "../../components/Archive.astro";
|
||||
---
|
||||
|
||||
<Layout title={`标签 ${tags}`} description={Description}>
|
||||
<section class="vh-container">
|
||||
<Archive articleList={articleList} />
|
||||
<Aside />
|
||||
</section>
|
||||
</Layout>
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
// 页面 Info
|
||||
import SITE_CONFIG from "../../config";
|
||||
const { Description } = SITE_CONFIG;
|
||||
// Aside组件
|
||||
import Aside from "../../components/Aside.astro";
|
||||
// 公共 Layout
|
||||
import Layout from "../../layouts/Layout.astro";
|
||||
// 评论组件
|
||||
import Comment from "../../components/Comment.astro";
|
||||
// 动态说说样式
|
||||
import "../../styles/Talking.less";
|
||||
---
|
||||
|
||||
<Layout title="动态" description={Description} activeNav="talking">
|
||||
<section class="vh-container">
|
||||
<section class="vh-talking">
|
||||
<header class="vh-page-header">
|
||||
<h1>动态 🥫</h1>
|
||||
<p>记录美好生活.</p>
|
||||
</header>
|
||||
<main></main>
|
||||
<Comment />
|
||||
</section>
|
||||
<Aside />
|
||||
</section>
|
||||
|
||||
<script>
|
||||
import updateRouter from "../../utils/updateRouter";
|
||||
import TalkingInit from "../../scripts/Talking";
|
||||
// 进入页面时初始化
|
||||
updateRouter("afterMount", TalkingInit);
|
||||
</script>
|
||||
</Layout>
|
||||
@@ -0,0 +1,71 @@
|
||||
// src/plugins/remark-note.js
|
||||
import { visit } from 'unist-util-visit';
|
||||
|
||||
// 处理函数
|
||||
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.children && node.children.length > 0) node.children.forEach((child: any) => nodeTreeFmt(child, node));
|
||||
}
|
||||
|
||||
// 处理标签
|
||||
const remarkNote = () => {
|
||||
return (tree: any) => {
|
||||
visit(tree, (node, index, parent) => {
|
||||
const { type, name, attributes } = node;
|
||||
// 处理组件
|
||||
if (type == 'textDirective' || type == 'leafDirective' || type == 'containerDirective') {
|
||||
// 设置 HTML 标签和 class
|
||||
const data = node.data || (node.data = {});
|
||||
const hProperties = data.hProperties || (data.hProperties = {});
|
||||
// 根据指令类型设置标签
|
||||
data.hName = name == 'btn' ? 'a' : 'section';
|
||||
// 这是 a 标签
|
||||
attributes.link && (hProperties.href = attributes.link);
|
||||
// 校验相册元素
|
||||
if (name == 'picture') {
|
||||
node.children = node.children.flatMap((child: any) => (child.type === 'paragraph' ? child.children : child));
|
||||
}
|
||||
// 处理 video 组件
|
||||
if (name.startsWith('vh')) {
|
||||
Object.keys(node.attributes).forEach((i: any) => (hProperties[`data-${i}`] = node.attributes[i]));
|
||||
}
|
||||
// 处理 note 内容换行问题
|
||||
if (name == 'note') {
|
||||
nodeTreeFmt(node, parent);
|
||||
};
|
||||
// 设置 class
|
||||
hProperties.class = `vh-node vh-${name}${attributes.type ? ` ${name}-${attributes.type}` : ''}`;
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Pre中的 Copy 按钮
|
||||
const addClassNames = () => {
|
||||
return (tree: any) => {
|
||||
visit(tree, (node, index, parent) => {
|
||||
// 处理 a 标签
|
||||
if (node.tagName === 'a') {
|
||||
node.properties.target = '_blank', node.properties.rel = 'noopener nofollow'
|
||||
node.children = [{ type: 'element', tagName: 'span', children: node.children || [] }];
|
||||
}
|
||||
// 处理代码快
|
||||
if (node.tagName === 'pre') {
|
||||
const divNode = { type: 'element', tagName: 'section', properties: { class: 'vh-code-box' }, children: [{ type: 'element', tagName: 'span', properties: { class: 'vh-code-copy' } }, node] };
|
||||
// 替换父节点的 children 中的 pre 节点为新的 div 节点
|
||||
if (parent && index !== null) parent.children.splice(index, 1, divNode);
|
||||
}
|
||||
// 处理图片
|
||||
if (node.tagName === 'img') {
|
||||
// 添加 class 和 loading 属性
|
||||
node.properties.class = 'vh-article-img';
|
||||
node.properties['data-vh-lz-src'] = node.properties.src;
|
||||
node.properties.src = '/assets/images/lazy-loading.webp';
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
export { remarkNote, addClassNames }
|
||||
@@ -0,0 +1,38 @@
|
||||
// 灯箱JS初始化======
|
||||
import "../../public/assets/js/view-image.min.js";
|
||||
declare const ViewImage: any;
|
||||
// 灯箱JS初始化======
|
||||
// Pre 滚动条======
|
||||
import "overlayscrollbars/overlayscrollbars.css";
|
||||
import { OverlayScrollbars } from "overlayscrollbars";
|
||||
// Pre 滚动条======
|
||||
// Pre Code 代码复制功能======
|
||||
let copyText = null;
|
||||
// Pre Code 代码复制功能======
|
||||
const ArticleInit = () => {
|
||||
// 灯箱JS初始化======
|
||||
ViewImage && ViewImage.init("main>.vh-container>article.vh-article-main img.vh-article-img");
|
||||
// 灯箱JS初始化======
|
||||
// Pre 滚动条======
|
||||
document.querySelectorAll("section.vh-code-box>pre.astro-code").forEach((i: any) => {
|
||||
OverlayScrollbars(i, { scrollbars: { autoHide: "leave", autoHideDelay: 500, autoHideSuspend: false } });
|
||||
});
|
||||
// Pre 滚动条======
|
||||
// Pre Code 代码复制功能======
|
||||
document.querySelectorAll("section.vh-code-box>span.vh-code-copy").forEach((i: any) => {
|
||||
i.vhTimer = null;
|
||||
i.addEventListener("click", async () => {
|
||||
if (i.vhTimer) clearTimeout(i.vhTimer);
|
||||
copyText = i.parentElement.querySelector("pre.astro-code code")?.innerText;
|
||||
if (!copyText) return;
|
||||
await navigator.clipboard.writeText(copyText);
|
||||
i.classList.add("success");
|
||||
i.vhTimer = setTimeout(() => {
|
||||
i.classList.remove("success");
|
||||
i.vhTimer = null;
|
||||
}, 1000);
|
||||
});
|
||||
});
|
||||
// Pre Code 代码复制功能======
|
||||
}
|
||||
export default ArticleInit;
|
||||
@@ -0,0 +1,42 @@
|
||||
|
||||
// 滚动条高度变化事件======
|
||||
const scrollChangeFn = () => {
|
||||
const scrollHeight = document.documentElement.scrollHeight;
|
||||
const clientHeight = window.innerHeight;
|
||||
const percentage = (window.scrollY / (scrollHeight - clientHeight)) * 100;
|
||||
// 显示隐藏
|
||||
backTop.classList[percentage <= 0 ? "remove" : "add"]("active");
|
||||
// 进度为 不在范围内
|
||||
if (percentage < 0 || percentage > 100) return;
|
||||
// 进度不为 0
|
||||
circle.style.strokeDashoffset = circumference - (percentage / 100) * circumference;
|
||||
};
|
||||
// 返回顶部事件
|
||||
const backTopFn = () => window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
// 页面更新,初始化函数======
|
||||
// 回顶部DOM
|
||||
let backTop: any = document.querySelector(".vh-back-top");
|
||||
// 彩虹圈圈 DOM
|
||||
let circle: any = document.querySelector(".vh-back-top>svg>circle");
|
||||
const circumference = 2 * Math.PI * 10;
|
||||
const BackTopInitFn = () => {
|
||||
// 更新 彩虹圈圈 DOM
|
||||
circle = document.querySelector(".vh-back-top>svg>circle");
|
||||
// 更新 回顶部DOM
|
||||
backTop = document.querySelector(".vh-back-top");
|
||||
// BackTop 圈圈初始化
|
||||
circle.style.strokeDasharray = circumference;
|
||||
circle.style.strokeDashoffset = circumference;
|
||||
// 移除并添加BackTop 事件======
|
||||
backTop.removeEventListener("click", backTopFn);
|
||||
backTop.addEventListener("click", backTopFn);
|
||||
// 移除并添加BackTop 事件======
|
||||
// 移除并添加ScrollChange 事件======
|
||||
window.removeEventListener("scroll", scrollChangeFn);
|
||||
window.addEventListener("scroll", scrollChangeFn);
|
||||
// 移除并添加ScrollChange 事件======
|
||||
// 触发 scrollChangeFn
|
||||
scrollChangeFn();
|
||||
};
|
||||
|
||||
export default BackTopInitFn;
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
import vh from 'vh-plugin'
|
||||
import { $GET } from '../utils/index'
|
||||
// 图片懒加载
|
||||
import vhLzImgInit from "../scripts/vhLazyImg";
|
||||
|
||||
const LinksInit = async () => {
|
||||
const linksDOM = document.querySelector('.vh-container>.vh-links>main')
|
||||
if (!linksDOM) return;
|
||||
vh.showLoading();
|
||||
try {
|
||||
const res = await $GET('https://tools-api.vvhan.com/blog/link');
|
||||
linksDOM.innerHTML = res.map((i: any) => `<a href="${i.link}" target="_blank"><img class="avatar" src="${i.avatar}" /><section class="link-info"><span>${i.name}</span><p>${i.descr}</p></section></a>`).join('');
|
||||
// 图片懒加载
|
||||
vhLzImgInit();
|
||||
} catch {
|
||||
vh.Toast('获取数据失败')
|
||||
} finally {
|
||||
vh.hideLoading();
|
||||
}
|
||||
}
|
||||
export default LinksInit;
|
||||
@@ -0,0 +1,22 @@
|
||||
import SITE_CONFIG from "../config";
|
||||
const { vhMusicApi } = SITE_CONFIG;
|
||||
import { $GET } from '../utils/index'
|
||||
import { LoadScript, LoadStyle } from "../utils/index";
|
||||
|
||||
declare const APlayer: any;
|
||||
const musicInit = async (musicList: any[]) => {
|
||||
const musicDOM: any = document.querySelectorAll(".vh-node.vh-vhMusic");
|
||||
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 LoadScript("https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/aplayer/1.10.1/APlayer.min.js");
|
||||
musicDOM.forEach(async (container: any) => {
|
||||
const { type, id, list } = container.dataset;
|
||||
const audio = await $GET(`${vhMusicApi}?server=${type}&type=${id ? 'song' : 'playlist'}&id=${id ? id : list}&r=${Math.random()}`);
|
||||
const ap = new APlayer({ container, audio, lrcType: 3 });
|
||||
musicList.push(ap);
|
||||
});
|
||||
};
|
||||
|
||||
export default musicInit;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import vh from 'vh-plugin'
|
||||
import { $GET } from '../utils/index'
|
||||
|
||||
// 更新数据
|
||||
let searchJson: any[] = [];
|
||||
const getSearchJson = async () => (searchJson = await $GET('/vh-search.json'))
|
||||
|
||||
// 搜索
|
||||
const searchFn = async (value: string) => {
|
||||
if (searchJson.length < 1) await getSearchJson();
|
||||
// 渲染页面
|
||||
renderSearch(findAndModifyElements(searchJson, value))
|
||||
}
|
||||
|
||||
// 关键词匹配
|
||||
const findAndModifyElements = (arr: any[], keyword: string) => {
|
||||
if ((keyword || '') == '') return []
|
||||
return arr
|
||||
.filter(item => item.content.includes(keyword))
|
||||
.map(item => {
|
||||
const content = item.content;
|
||||
const keywordIndex = content.indexOf(keyword);
|
||||
const start = Math.max(0, keywordIndex - 50);
|
||||
const end = Math.min(content.length, keywordIndex + keyword.length + 50);
|
||||
let newContent = content.substring(start, end);
|
||||
newContent = newContent.replace(new RegExp(keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), "g"), `<span>${keyword}</span>`);
|
||||
return { ...item, content: newContent };
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染页面
|
||||
let searchHTML = '';
|
||||
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('');
|
||||
document.querySelector('.vh-header>.main>.vh-search>main>.vh-search-list')!.innerHTML = searchHTML;
|
||||
}
|
||||
|
||||
// 截流
|
||||
let fnTimer: any = null;
|
||||
const searchInputChange = (v: any) => {
|
||||
const value = v.target.value;
|
||||
if (fnTimer) clearTimeout(fnTimer);
|
||||
fnTimer = setTimeout(() => searchFn(value), 266);
|
||||
}
|
||||
|
||||
export { searchFn, searchInputChange };
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
import vh from 'vh-plugin'
|
||||
import { $GET } from '../utils/index'
|
||||
// 图片懒加载
|
||||
import vhLzImgInit from "../scripts/vhLazyImg";
|
||||
// 灯箱JS初始化======
|
||||
import "../../public/assets/js/view-image.min.js";
|
||||
declare const ViewImage: any;
|
||||
// 灯箱JS初始化======
|
||||
|
||||
const TalkingInit = async () => {
|
||||
const talkingDOM = document.querySelector('.vh-container>.vh-talking>main')
|
||||
if (!talkingDOM) return;
|
||||
vh.showLoading();
|
||||
try {
|
||||
const res = await $GET('https://tools-api.vvhan.com/blog/feeling');
|
||||
talkingDOM.innerHTML = res.map((i: any) => `<article><header><img data-vh-lz-src="https://q1.qlogo.cn/g?b=qq&nk=1655466387&s=640" /><p class="info"><span>.𝙃𝙖𝙣</span><time>${i.date}</time></p></header><section class="main">${i.content}</section><footer>${i.tags.map((tag: any) => `<span>${tag}</span>`).join('')}</footer></article>`).join('');
|
||||
// 图片懒加载
|
||||
vhLzImgInit();
|
||||
// 灯箱JS初始化======
|
||||
ViewImage && ViewImage.init(".vh-talking>main>article>.main img");
|
||||
// 灯箱JS初始化======
|
||||
} catch {
|
||||
vh.Toast('获取数据失败')
|
||||
} finally {
|
||||
vh.hideLoading();
|
||||
}
|
||||
}
|
||||
export default TalkingInit;
|
||||
@@ -0,0 +1,37 @@
|
||||
import { LoadScript } from "../utils/index";
|
||||
// 初始化视频播放器
|
||||
declare const DPlayer: any;
|
||||
declare const Hls: any;
|
||||
const videoInit = async (videoList: any[]) => {
|
||||
const videoDOM: any = document.querySelectorAll(".vh-node.vh-vhVideo");
|
||||
if (videoDOM.length === 0) return;
|
||||
// 载入依赖
|
||||
await LoadScript("https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/dplayer/1.26.0/DPlayer.min.js");
|
||||
await LoadScript("https://registry.npmmirror.com/hls.js/1.5.20/files/dist/hls.min.js");
|
||||
videoDOM.forEach((i: any) => {
|
||||
const dp = new DPlayer({
|
||||
container: i,
|
||||
logo: "/assets/images/logo.png",
|
||||
volume: 0.7,
|
||||
mutex: true,
|
||||
video: {
|
||||
url: i.getAttribute("data-url"),
|
||||
type: "auto",
|
||||
customType: {
|
||||
hls: (video: any) => {
|
||||
if (Hls.isSupported()) {
|
||||
const hls = new Hls({ enableWorker: true, autoStartLoad: true, capLevelToPlayerSize: true });
|
||||
hls.loadSource(video.src);
|
||||
hls.attachMedia(video);
|
||||
} else if (video.canPlayType("application/vnd.apple.mpegurl")) {
|
||||
video.src = i.getAttribute("data-url");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
videoList.push(dp);
|
||||
});
|
||||
};
|
||||
|
||||
export default videoInit;
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
// 图片懒加载
|
||||
import LazyLoad from "vanilla-lazyload";
|
||||
const vhLzImgInit = () => {
|
||||
document.querySelectorAll("main>.vh-container>:not(aside) img:not(.view-image-container)").forEach((i: any) => {
|
||||
// 是否包含data-vh-lz-src
|
||||
if (!i.hasAttribute("data-vh-lz-src")) {
|
||||
i.setAttribute("data-vh-lz-src", i.getAttribute("src"));
|
||||
i.setAttribute("src", '/assets/images/lazy-loading.webp');
|
||||
}
|
||||
});
|
||||
new LazyLoad({
|
||||
elements_selector: "img:not(.view-image-container)",
|
||||
threshold: 0,
|
||||
data_src: "vh-lz-src"
|
||||
});
|
||||
}
|
||||
|
||||
export default vhLzImgInit;
|
||||
@@ -0,0 +1,48 @@
|
||||
.vh-about {
|
||||
gap: 1.25rem;
|
||||
|
||||
&>main {
|
||||
box-sizing: border-box;
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
background: #fff;
|
||||
box-shadow: 0 3px 8px 6px rgba(7, 17, 27, 0.05);
|
||||
width: 100%;
|
||||
min-height: 5.5rem;
|
||||
overflow: hidden;
|
||||
|
||||
&>h3 {
|
||||
box-sizing: border-box;
|
||||
padding: 0.88rem 0;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
&>.language-tool {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.56rem;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: max-content;
|
||||
|
||||
&>a {
|
||||
width: max-content;
|
||||
max-width: 100%;
|
||||
height: max-content;
|
||||
line-height: 1;
|
||||
overflow: hidden;
|
||||
transition: transform 0.16s ease-in-out;
|
||||
|
||||
&>img {
|
||||
border-bottom: 1px solid #2563eb;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-0.12rem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
section.vh-container {
|
||||
&>section.vh-archive-main {
|
||||
flex: 1;
|
||||
padding: 2rem 1rem;
|
||||
border-radius: 1rem;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
|
||||
&>.archive-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
|
||||
&>.archive-list-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&>p,
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 2.5rem;
|
||||
border-radius: 1rem;
|
||||
font-size: 1rem;
|
||||
transition: background .16s;
|
||||
|
||||
|
||||
&:not(.title) {
|
||||
|
||||
&:hover {
|
||||
background: #F5F6F8;
|
||||
|
||||
&>i {
|
||||
&::before {
|
||||
height: 50.66%;
|
||||
}
|
||||
}
|
||||
|
||||
&>span {
|
||||
padding-left: 0.28rem;
|
||||
}
|
||||
}
|
||||
|
||||
&>em {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #00000080;
|
||||
}
|
||||
|
||||
&>i {
|
||||
|
||||
&::before {
|
||||
width: 0.25rem;
|
||||
height: 0.25rem;
|
||||
border: none;
|
||||
border-radius: 0.25rem;
|
||||
background: #4E667F;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 0.125rem;
|
||||
height: 1rem;
|
||||
left: calc(50% - 0.0625rem);
|
||||
transform: translateY(-50%);
|
||||
border-left: 2px dashed rgba(0, 0, 0, .1);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&>span {
|
||||
font-size: 1rem;
|
||||
color: #000000;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&>em {
|
||||
flex-shrink: 0;
|
||||
width: 5.05rem;
|
||||
height: 3.75rem;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
line-height: 3.75rem;
|
||||
text-align: right;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
&>i {
|
||||
position: relative;
|
||||
width: 2.5rem;
|
||||
height: 100%;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: block;
|
||||
width: 0.36rem;
|
||||
height: 0.36rem;
|
||||
border: solid 0.28rem #0000006D;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
transition: all .3s;
|
||||
}
|
||||
}
|
||||
|
||||
&>span {
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
color: #0000004D;
|
||||
font-weight: 500;
|
||||
text-overflow: ellipsis;
|
||||
transition: all .16s;
|
||||
}
|
||||
|
||||
&>cite {
|
||||
margin-left: auto;
|
||||
width: 7.8rem;
|
||||
font-size: 0.875rem;
|
||||
color: #0000004D;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@media screen and (max-width: 1198px) {
|
||||
section.vh-container {
|
||||
&>section.vh-archive-main {
|
||||
&>.archive-list {
|
||||
&>.archive-list-item {
|
||||
|
||||
&>p,
|
||||
a {
|
||||
&>cite {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 888px) {
|
||||
section.vh-container {
|
||||
&>section.vh-archive-main {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
|
||||
&>.archive-list {
|
||||
&>.archive-list-item {
|
||||
|
||||
&>p,
|
||||
a {
|
||||
|
||||
&:not(.title) {
|
||||
&>em {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
&>span {
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
}
|
||||
|
||||
&>em {
|
||||
width: 3rem;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
&>i {
|
||||
width: 1.8rem;
|
||||
}
|
||||
|
||||
&>span {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,630 @@
|
||||
section.vh-container {
|
||||
&>article.vh-article-main {
|
||||
flex: 1;
|
||||
padding: 1rem 0;
|
||||
min-width: 0;
|
||||
|
||||
// 文章内容样式开始=========================
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
box-sizing: border-box;
|
||||
margin: 1.188rem 0 0.36rem;
|
||||
font-size: 1rem;
|
||||
width: max-content;
|
||||
max-width: 100%;
|
||||
word-break: break-all;
|
||||
|
||||
&:hover {
|
||||
&::before {
|
||||
color: #60a5fa7f;
|
||||
}
|
||||
}
|
||||
|
||||
&::before {
|
||||
padding-right: 0.56rem;
|
||||
content: "#";
|
||||
color: #33333388;
|
||||
transition: color 0.16s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
padding-bottom: 1rem;
|
||||
font-size: 1.58rem;
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
// p标签样式
|
||||
// ul标签样式
|
||||
p,
|
||||
ul,
|
||||
ol {
|
||||
margin: 0.618rem 0;
|
||||
font-size: 0.9375rem;
|
||||
line-height: 1.6;
|
||||
|
||||
li {
|
||||
color: #33333388;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
// 其下面的 a 标签样式
|
||||
a {
|
||||
box-shadow: inset 0 -.12em #60a5fa;
|
||||
transition: box-shadow .2s ease-in-out, color .2s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
box-shadow: inset 0 -1.5em #60a5fa7f
|
||||
}
|
||||
}
|
||||
|
||||
// 其下面的code标签样式
|
||||
code {
|
||||
background-color: #edeef3;
|
||||
padding: .125rem .375rem;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
|
||||
/* 全局表格样式 */
|
||||
table {
|
||||
box-sizing: border-box;
|
||||
margin: 1rem 0;
|
||||
width: 100%;
|
||||
// border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
font-size: 0.9rem;
|
||||
background: #fff;
|
||||
border: 1px solid #EEEEEE;
|
||||
border-radius: 0.38rem;
|
||||
overflow: hidden;
|
||||
|
||||
a {
|
||||
color: #49B1F5;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
background-color: #f8f9fa;
|
||||
padding: 0.75rem;
|
||||
border-bottom: solid 1px #EEEEEE;
|
||||
border-right: solid 1px #EEEEEE;
|
||||
|
||||
&:nth-last-of-type(1) {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0.618rem 0.75rem;
|
||||
vertical-align: top;
|
||||
border-bottom: solid 1px #EEEEEE;
|
||||
border-right: solid 1px #EEEEEE;
|
||||
|
||||
&:nth-last-of-type(1) {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
|
||||
tr {
|
||||
transition: background-color 0.16s ease-in-out;
|
||||
|
||||
&:nth-last-of-type(1) {
|
||||
td {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// table {
|
||||
// box-sizing: border-box;
|
||||
// display: table;
|
||||
// width: 100%;
|
||||
// border-radius: 5px;
|
||||
// border-spacing: 0;
|
||||
// border-collapse: separate;
|
||||
// border-top: 1px solid var(--light-grey);
|
||||
// border-left: 1px solid var(--light-grey);
|
||||
// empty-cells: show;
|
||||
|
||||
|
||||
// thead {
|
||||
// background: rgba(153, 169, 191, .1);
|
||||
|
||||
// &>tr:first-child th:first-child {
|
||||
// border-top-left-radius: 5px
|
||||
// }
|
||||
|
||||
// &>tr:first-child th:last-child {
|
||||
// border-top-right-radius: 5px
|
||||
// }
|
||||
// }
|
||||
|
||||
// tbody {
|
||||
// &>tr:last-child td:first-child {
|
||||
// border-bottom-left-radius: 5px
|
||||
// }
|
||||
|
||||
// &>tr:last-child td:last-child {
|
||||
// border-bottom-right-radius: 5px
|
||||
// }
|
||||
// }
|
||||
|
||||
// td,
|
||||
// table th {
|
||||
// padding: 6px 12px;
|
||||
// border: 1px solid var(--light-grey);
|
||||
// border-top: none;
|
||||
// border-left: none;
|
||||
// vertical-align: middle
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
&>header {
|
||||
padding: 2rem 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
&>h1 {
|
||||
width: 100%;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&>.article-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.18rem;
|
||||
font-size: 0.88rem;
|
||||
|
||||
&>.article-meta-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.56rem;
|
||||
|
||||
&>svg {
|
||||
height: 0.86rem;
|
||||
width: 0.86rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义组件样式
|
||||
.vh-node {
|
||||
margin: 0.36rem 0;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
overflow: hidden;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
// :::note
|
||||
&.vh-note {
|
||||
padding: 0.888rem;
|
||||
border-left: 5px solid #eee;
|
||||
border-left-color: #777;
|
||||
border-radius: 3px;
|
||||
background: #f7f7f7;
|
||||
|
||||
p {
|
||||
font-size: 0.875rem;
|
||||
line-height: 2;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
// ::btn
|
||||
&.vh-btn {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.618rem;
|
||||
padding: 0 1.18rem;
|
||||
width: max-content;
|
||||
max-width: 100%;
|
||||
height: 2.28rem;
|
||||
border: solid 1px #333;
|
||||
border-radius: 0.28rem;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.18s ease-in-out;
|
||||
z-index: 1;
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&::before {
|
||||
flex-shrink: 0;
|
||||
content: '';
|
||||
background-color: #000;
|
||||
mask: url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20448%20512%22%3E%3Cpath%20d%3D%22M364.2%2083.8c-24.4-24.4-64-24.4-88.4%200l-184%20184c-42.1%2042.1-42.1%20110.3%200%20152.4s110.3%2042.1%20152.4%200l152-152c10.9-10.9%2028.7-10.9%2039.6%200s10.9%2028.7%200%2039.6l-152%20152c-64%2064-167.6%2064-231.6%200s-64-167.6%200-231.6l184-184c46.3-46.3%20121.3-46.3%20167.6%200s46.3%20121.3%200%20167.6l-176%20176c-28.6%2028.6-75%2028.6-103.6%200s-28.6-75%200-103.6l144-144c10.9-10.9%2028.7-10.9%2039.6%200s10.9%2028.7%200%2039.6l-144%20144c-6.7%206.7-6.7%2017.7%200%2024.4s17.7%206.7%2024.4%200l176-176c24.4-24.4%2024.4-64%200-88.4z%22%2F%3E%3C%2Fsvg%3E') no-repeat center center;
|
||||
background-size: contain;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
background: #EDEEF366;
|
||||
transition: height 0.16s ease-in-out;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
|
||||
&>span {
|
||||
box-sizing: border-box;
|
||||
color: #000;
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&.success {
|
||||
border-color: #01C4B6;
|
||||
|
||||
&>span {
|
||||
color: #01C4B6;
|
||||
}
|
||||
|
||||
&::before {
|
||||
background-color: #01C4B6;
|
||||
}
|
||||
}
|
||||
|
||||
&.info {
|
||||
border-color: #3275B4;
|
||||
|
||||
&>span {
|
||||
color: #3275B4;
|
||||
}
|
||||
|
||||
&::before {
|
||||
background-color: #3275B4;
|
||||
}
|
||||
}
|
||||
|
||||
&.warning {
|
||||
border-color: #DD8636;
|
||||
|
||||
&>span {
|
||||
color: #DD8636;
|
||||
}
|
||||
|
||||
&::before {
|
||||
background-color: #DD8636;
|
||||
}
|
||||
}
|
||||
|
||||
&.error {
|
||||
border-color: #DE3C3D;
|
||||
|
||||
&>span {
|
||||
color: #DE3C3D;
|
||||
}
|
||||
|
||||
&::before {
|
||||
background-color: #DE3C3D;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// :::picture
|
||||
&.vh-picture {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(30%, 1fr));
|
||||
gap: .36rem;
|
||||
|
||||
// 其下面的图片
|
||||
&>img.vh-article-img {
|
||||
width: 100%;
|
||||
aspect-ratio: 3 / 2;
|
||||
border: solid 1px #E8E8E8;
|
||||
border-radius: 5px;
|
||||
object-fit: cover;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
// ::vhVideo
|
||||
&.vh-vhVideo {
|
||||
margin: 1.188rem 0;
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
border: solid 1px #E8E8E8;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// ::vhMusic
|
||||
&.vh-vhMusic {
|
||||
margin: 1.188rem 0;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
min-height: 1rem;
|
||||
|
||||
&:not(.aplayer) {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
width: 1.66rem;
|
||||
height: 1.66rem;
|
||||
background: url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20d%3D%22M304%2048a48%2048%200%201%200%20-96%200%2048%2048%200%201%200%2096%200zm0%20416a48%2048%200%201%200%20-96%200%2048%2048%200%201%200%2096%200zM48%20304a48%2048%200%201%200%200-96%2048%2048%200%201%200%200%2096zm464-48a48%2048%200%201%200%20-96%200%2048%2048%200%201%200%2096%200zM142.9%20437A48%2048%200%201%200%2075%20369.1%2048%2048%200%201%200%20142.9%20437zm0-294.2A48%2048%200%201%200%2075%2075a48%2048%200%201%200%2067.9%2067.9zM369.1%20437A48%2048%200%201%200%20437%20369.1%2048%2048%200%201%200%20369.1%20437z%22%2F%3E%3C%2Fsvg%3E') no-repeat center center;
|
||||
background-size: cover;
|
||||
animation: vhRotate 1.66s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.aplayer-body {
|
||||
.aplayer-info {
|
||||
.aplayer-lrc {
|
||||
.aplayer-lrc-contents {
|
||||
p {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 页面图片样式
|
||||
img.vh-article-img {
|
||||
box-sizing: border-box;
|
||||
padding: .36rem;
|
||||
width: max-content;
|
||||
max-width: 100%;
|
||||
max-height: 36rem;
|
||||
border: solid 1px #E8E8E8;
|
||||
border-radius: 5px;
|
||||
cursor: zoom-in;
|
||||
cursor: -moz-zoom-in;
|
||||
cursor: -webkit-zoom-in;
|
||||
|
||||
&:not(.loaded) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
// 文章内代码块
|
||||
section.vh-code-box {
|
||||
margin: 1.188rem 0;
|
||||
position: relative;
|
||||
|
||||
&>span.vh-code-copy {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
right: 0.66rem;
|
||||
top: 0.6rem;
|
||||
width: 1.8rem;
|
||||
height: 1.8rem;
|
||||
border: solid 1px rgba(125, 125, 125, .2);
|
||||
border-radius: 0.5rem;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
opacity: 0.66;
|
||||
transition: all .16s;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(125, 125, 125, .2) url('data:image/svg+xml,%0A%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%20-960%20960%20960%22%3E%3Cpath%20fill%3D%22%23808080%22%20d%3D%22M368.37-237.37q-34.48%200-58.74-24.26-24.26-24.26-24.26-58.74v-474.26q0-34.48%2024.26-58.74%2024.26-24.26%2058.74-24.26h378.26q34.48%200%2058.74%2024.26%2024.26%2024.26%2024.26%2058.74v474.26q0%2034.48-24.26%2058.74-24.26%2024.26-58.74%2024.26H368.37Zm0-83h378.26v-474.26H368.37v474.26Zm-155%20238q-34.48%200-58.74-24.26-24.26-24.26-24.26-58.74v-515.76q0-17.45%2011.96-29.48%2011.97-12.02%2029.33-12.02t29.54%2012.02q12.17%2012.03%2012.17%2029.48v515.76h419.76q17.45%200%2029.48%2011.96%2012.02%2011.97%2012.02%2029.33t-12.02%2029.54q-12.03%2012.17-29.48%2012.17H213.37Zm155-238v-474.26%20474.26Z%22%2F%3E%3C%2Fsvg%3E') no-repeat center center;
|
||||
background-size: 1.16rem 1.16rem;
|
||||
opacity: 0;
|
||||
transition: opacity 0.16s;
|
||||
}
|
||||
|
||||
&::after {
|
||||
background: #01C4B6 url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%20-960%20960%20960%22%3E%3Cpath%20fill%3D%22%23fff%22%20d%3D%22m389-377.13%20294.7-294.7q12.58-12.67%2029.52-12.67%2016.93%200%2029.61%2012.67%2012.67%2012.68%2012.67%2029.53%200%2016.86-12.28%2029.14L419.07-288.41q-12.59%2012.67-29.52%2012.67-16.94%200-29.62-12.67L217.41-430.93q-12.67-12.68-12.79-29.45-.12-16.77%2012.55-29.45%2012.68-12.67%2029.62-12.67%2016.93%200%2029.28%2012.67L389-377.13Z%22%2F%3E%3C%2Fsvg%3E') no-repeat center center;
|
||||
background-size: 1.16rem 1.16rem;
|
||||
}
|
||||
|
||||
&::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.success {
|
||||
opacity: 1;
|
||||
|
||||
&::before {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&::after {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
&>pre.astro-code {
|
||||
box-sizing: border-box;
|
||||
padding: 0.888rem;
|
||||
border-radius: 0.618rem;
|
||||
height: max-content;
|
||||
max-height: 888px;
|
||||
background: #EDEEF399 !important;
|
||||
|
||||
code {
|
||||
box-sizing: border-box;
|
||||
counter-reset: line;
|
||||
|
||||
&>span {
|
||||
box-sizing: border-box;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.66;
|
||||
|
||||
&::before {
|
||||
counter-increment: line;
|
||||
content: counter(line);
|
||||
display: inline-block;
|
||||
width: 1rem;
|
||||
padding-right: 0.618rem;
|
||||
margin-right: 0.888rem;
|
||||
border-right: 1px solid #e2e2e3;
|
||||
color: #939296;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 文章内容样式结束=========================
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 滚动条样式
|
||||
.os-theme-dark.os-scrollbar {
|
||||
&>.os-scrollbar-track {
|
||||
&>.os-scrollbar-handle {
|
||||
background: #A3A8AF88;
|
||||
|
||||
&:hover {
|
||||
background: #A3A8AF66;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 右侧滚动条
|
||||
&.os-scrollbar-vertical {
|
||||
&>.os-scrollbar-track {
|
||||
&>.os-scrollbar-handle {
|
||||
&:hover {
|
||||
width: 0.56rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 底部滚动条
|
||||
&.os-scrollbar-horizontal {
|
||||
&>.os-scrollbar-track {
|
||||
&>.os-scrollbar-handle {
|
||||
&:hover {
|
||||
height: 0.56rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media screen and (max-width: 999px) {
|
||||
section.vh-container {
|
||||
&>article.vh-article-main {
|
||||
&>header {
|
||||
padding-top: 0;
|
||||
|
||||
&>h1 {
|
||||
font-size: 1.36rem;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.36rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.26rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.18rem;
|
||||
}
|
||||
|
||||
|
||||
.vh-node {
|
||||
&.vh-picture {
|
||||
grid-template-columns: repeat(auto-fit, minmax(48%, 1fr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 旋转动画
|
||||
@keyframes vhRotate {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
/* 起始角度 */
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
/* 结束角度 */
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
@import url('/assets/font/index.css');
|
||||
|
||||
:root {
|
||||
--vh-animation-delay: 0ms;
|
||||
--vh-padding-top: calc(66px + 1rem);
|
||||
--vh-back-top: calc((calc(100vw - 2rem) - min((100vw - 2rem), 1388px)) / 2 + 1rem);
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
blockquote {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
|
||||
a {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
pre,
|
||||
code {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: 'SF Mono' !important;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
* {
|
||||
// IOS 点击阴影
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
// 隐藏滚动条
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
// 消除边框
|
||||
outline: none; // 隐藏滚动条
|
||||
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
|
||||
&>body {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
padding: var(--vh-padding-top) 1rem 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100vw;
|
||||
height: max-content;
|
||||
font-family: 'MiSans', serif;
|
||||
font-weight: 400;
|
||||
cursor: url(/assets/images/default.cur), default;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
// 其他页面Header 封装
|
||||
header.vh-page-header {
|
||||
box-sizing: border-box;
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
border-radius: 0.5rem;
|
||||
background: #fff;
|
||||
box-shadow: 0 3px 8px 6px rgba(7, 17, 27, 0.05);
|
||||
overflow: hidden;
|
||||
|
||||
&>h1 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 动画效果
|
||||
.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 {
|
||||
img[data-vh-lz-src] {
|
||||
transition: all 0.18s ease-in;
|
||||
|
||||
&:not(.loaded) {
|
||||
filter: blur(0.88rem) brightness(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 加载进度条样式
|
||||
#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) {
|
||||
// 手机端 禁止选择
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
body {
|
||||
&::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: #fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' width='8' height='8' fill='none' stroke='rgb(0 0 0 / 0.1)'%3e%3cpath d='M0 .5H31.5V32'/%3e%3c/svg%3e");
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
&>main.vh-main {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
max-width: 1388px;
|
||||
height: max-content;
|
||||
|
||||
&>section.vh-container {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
padding-bottom: 5.66rem;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
gap: 1.8rem;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
|
||||
&>section:not(.article-list) {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media screen and (max-width: 888px) {
|
||||
body {
|
||||
&>main.vh-main {
|
||||
&>section.vh-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// 说说页评论
|
||||
.vh-links {
|
||||
gap: 1.25rem;
|
||||
|
||||
&>main {
|
||||
box-sizing: border-box;
|
||||
padding: 1rem;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
background: #fff;
|
||||
box-shadow: 0 3px 8px 6px rgba(7, 17, 27, 0.05);
|
||||
overflow: hidden;
|
||||
min-height: 5.5rem;
|
||||
|
||||
&>a {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
padding: 0 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
width: 100%;
|
||||
height: 5.5rem;
|
||||
border-radius: 0.88rem;
|
||||
transition: background-color 0.16s ease-in-out;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
|
||||
&:hover {
|
||||
background: #EDEEF3;
|
||||
|
||||
&>.avatar {
|
||||
transform: scale(1.2) rotate(8deg);
|
||||
}
|
||||
}
|
||||
|
||||
&>.avatar {
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
padding: 0.18rem;
|
||||
width: 3.36rem;
|
||||
height: 3.36rem;
|
||||
border-radius: 50%;
|
||||
border: solid 1px #E8E8E8;
|
||||
overflow: hidden;
|
||||
opacity: 1;
|
||||
transition: all .16s;
|
||||
}
|
||||
|
||||
&>.link-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 0.388rem;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
|
||||
&>span {
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
&>p {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
font-size: 0.81rem;
|
||||
color: #858585;
|
||||
line-height: 1rem;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1088px) {
|
||||
.vh-links {
|
||||
&>main {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 568px) {
|
||||
.vh-links {
|
||||
&>main {
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
// 说说页评论
|
||||
.vh-talking {
|
||||
min-height: 56vh;
|
||||
|
||||
&>main {
|
||||
margin: 1.25rem 0;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
|
||||
&>article {
|
||||
box-sizing: border-box;
|
||||
padding: 2.188rem 1.875rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 0.5rem;
|
||||
background: #fff;
|
||||
box-shadow: 0 3px 8px 6px rgba(7, 17, 27, 0.05);
|
||||
overflow: hidden;
|
||||
|
||||
&>header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.626rem;
|
||||
width: 100%;
|
||||
height: 2.5rem;
|
||||
|
||||
&>img {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&>.info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
line-height: 1.26rem;
|
||||
overflow: hidden;
|
||||
|
||||
&>span {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
&>time {
|
||||
font-size: 0.7rem;
|
||||
color: #858585;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 说说内容样式
|
||||
&>.main {
|
||||
box-sizing: border-box;
|
||||
padding: 1rem 0;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
aspect-ratio: 3 / 2;
|
||||
border-radius: 0.375rem;
|
||||
object-fit: contain;
|
||||
overflow: hidden;
|
||||
cursor: zoom-in;
|
||||
cursor: -moz-zoom-in;
|
||||
cursor: -webkit-zoom-in;
|
||||
}
|
||||
|
||||
&>.vh-img-flex {
|
||||
box-sizing: border-box;
|
||||
padding: 0.8rem 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(30%, 1fr));
|
||||
gap: 0.66rem;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
max-height: 50%;
|
||||
overflow: hidden;
|
||||
|
||||
&>img {
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
// 说说定位样式
|
||||
&>.geographical-location {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.2rem;
|
||||
font-style: normal;
|
||||
font-size: 14px;
|
||||
color: #49c7cc;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background: url('/public/assets/images/local.svg') no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&>footer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
|
||||
&>span {
|
||||
box-sizing: border-box;
|
||||
padding: 0 0.5rem;
|
||||
width: max-content;
|
||||
line-height: 1.46rem;
|
||||
border: 1px solid #49b1f5;
|
||||
border-radius: 2rem;
|
||||
color: #49b1f5;
|
||||
font-size: 0.72rem;
|
||||
transition: all .2s ease-in-out;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
animation: talking-tag-active 0.16s ease-in-out infinite;
|
||||
color: #fff;
|
||||
background-color: #49b1f5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 888px) {
|
||||
.vh-talking {
|
||||
&>main {
|
||||
&>article {
|
||||
padding: 1.06rem 0.88rem;
|
||||
|
||||
&>.main {
|
||||
&>.vh-img-flex {
|
||||
grid-template-columns: repeat(auto-fit, minmax(46%, 1fr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes talking-tag-active {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: translateX(-1.66px);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateX(1.66px);
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: translateX(-1.66px);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
section.article-list {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
padding-bottom: 6rem;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
|
||||
gap: 1.6rem;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
&>.vh-article-item {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: solid 1px #eee;
|
||||
border-radius: 1rem;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
|
||||
&>a.vh-article-link {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 0.618rem;
|
||||
overflow: hidden;
|
||||
transition: background 0.16s;
|
||||
|
||||
&:hover {
|
||||
&>.vh-article-banner {
|
||||
&>img {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&>.vh-article-banner {
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 12rem;
|
||||
overflow: hidden;
|
||||
|
||||
&>img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
// transition公共区域有动画效果
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&>.vh-article-desc {
|
||||
box-sizing: border-box;
|
||||
padding: 1.25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: max-content;
|
||||
overflow: hidden;
|
||||
|
||||
&>header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.36rem;
|
||||
|
||||
&>.vh-article-categories {
|
||||
box-sizing: border-box;
|
||||
width: max-content;
|
||||
font-size: 0.875rem;
|
||||
font-weight: bold;
|
||||
/* 默认渐变色起始 */
|
||||
--gradient-color-1: #a32cc4;
|
||||
/* 默认渐变色中间 */
|
||||
--gradient-color-2: #b65fcf;
|
||||
/* 默认渐变色结束 */
|
||||
--gradient-color-3: #e39ff6;
|
||||
background: linear-gradient(118deg, var(--gradient-color-1), var(--gradient-color-2) 26%, var(--gradient-color-3));
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
color: transparent;
|
||||
|
||||
&.vh-categories-Code {
|
||||
/* 默认渐变色起始 */
|
||||
--gradient-color-1: #f48600;
|
||||
/* 默认渐变色中间 */
|
||||
--gradient-color-2: #ffa12e;
|
||||
/* 默认渐变色结束 */
|
||||
--gradient-color-3: #ffe9c9;
|
||||
}
|
||||
|
||||
&.vh-categories-Daily {
|
||||
/* 默认渐变色起始 */
|
||||
--gradient-color-1: #6270ee;
|
||||
/* 默认渐变色中间 */
|
||||
--gradient-color-2: #2b90d9;
|
||||
/* 默认渐变色结束 */
|
||||
--gradient-color-3: #37dede;
|
||||
}
|
||||
|
||||
&.vh-categories-Software {
|
||||
/* 默认渐变色起始 */
|
||||
--gradient-color-1: #006633;
|
||||
/* 默认渐变色中间 */
|
||||
--gradient-color-2: #00cc66;
|
||||
/* 默认渐变色结束 */
|
||||
--gradient-color-3: #66ff99;
|
||||
}
|
||||
}
|
||||
|
||||
&>.title {
|
||||
margin: 0;
|
||||
font-size: 1.18rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.36;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&>.vh-article-excerpt {
|
||||
margin: 0.88rem 0 0.625rem;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.28rem;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
overflow: hidden;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&>footer {
|
||||
margin-top: auto;
|
||||
font-size: 0.875rem;
|
||||
color: #6E6E6E;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&>.vh-article-pagination {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
aside.vh-aside {
|
||||
flex-shrink: 0;
|
||||
position: sticky;
|
||||
top: var(--vh-padding-top);
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
width: 288px;
|
||||
height: max-content;
|
||||
|
||||
&>.vh-aside-item {
|
||||
box-sizing: border-box;
|
||||
padding: 0.75rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
border-radius: 1rem;
|
||||
background: #fff;
|
||||
color: #000;
|
||||
box-shadow: 0 3px 8px 6px rgba(7, 17, 27, 0.05);
|
||||
overflow: hidden;
|
||||
|
||||
|
||||
&>h3 {
|
||||
padding-bottom: 0.618rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.68rem;
|
||||
width: 100%;
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
width: 0.25rem;
|
||||
height: 1.16rem;
|
||||
border-radius: 0.5rem;
|
||||
background: #EDEEF3;
|
||||
}
|
||||
}
|
||||
|
||||
// 用户信息
|
||||
&.user {
|
||||
padding: 1.88rem 0;
|
||||
|
||||
&>.vh-aside-avatar {
|
||||
position: relative;
|
||||
width: 6.36rem;
|
||||
aspect-ratio: 1/1;
|
||||
border-radius: 1rem;
|
||||
cursor: pointer;
|
||||
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 {
|
||||
position: relative;
|
||||
font-size: 1.25rem;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
display: block;
|
||||
bottom: -0.16rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 36%;
|
||||
height: 0.26rem;
|
||||
border-radius: 3px;
|
||||
background: #EDEEF3;
|
||||
}
|
||||
}
|
||||
|
||||
&>.vh-aside-motto {
|
||||
font-size: 0.86rem;
|
||||
color: #A3A3A3;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&>.vh-aside-links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
&>.vh-aside-links-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.88rem;
|
||||
height: 1.88rem;
|
||||
border: solid 1px #333;
|
||||
background: #fff;
|
||||
border-radius: 0.38rem;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
background: #EDEEF366;
|
||||
transition: height 0.16s ease-in-out;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
&>img {
|
||||
width: 1.18rem;
|
||||
height: 1.18rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 分类
|
||||
&.categories {
|
||||
&>.vh-aside-categories {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
max-height: 15.56rem;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
&>a {
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
padding: 0.5rem 0.28rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: 2.36rem;
|
||||
border-radius: 0.38rem;
|
||||
background: #fff;
|
||||
transition: all 0.16s ease-in-out;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
padding-left: 0.66rem;
|
||||
background: #EDEEF388;
|
||||
|
||||
&>i {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&>i {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 1.75rem;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
border-radius: 0.38rem;
|
||||
background: #EDEEF388;
|
||||
transition: background 0.16s ease-in-out;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 最新文章
|
||||
&.articles {
|
||||
&>.vh-aside-articles {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
overflow: hidden;
|
||||
|
||||
&>a {
|
||||
box-sizing: border-box;
|
||||
padding: 0 0.625rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.625rem;
|
||||
width: 100%;
|
||||
height: 4.6rem;
|
||||
border-bottom: 1px dashed #f3f2f2;
|
||||
overflow: hidden;
|
||||
|
||||
&:nth-last-of-type(1) {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
||||
&>p {
|
||||
&>img {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&>p {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
|
||||
&.cover {
|
||||
flex-shrink: 0;
|
||||
width: 3.88rem;
|
||||
height: 3.88rem;
|
||||
border-radius: 0.288rem;
|
||||
overflow: hidden;
|
||||
|
||||
&>img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.16s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
&.info {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
|
||||
&>span {
|
||||
width: 100%;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&>time {
|
||||
color: #858585;
|
||||
font-size: 0.68rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 标签
|
||||
&.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 888px) {
|
||||
aside.vh-aside {
|
||||
position: static;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
section.vh-back-top {
|
||||
position: fixed;
|
||||
right: var(--vh-back-top);
|
||||
bottom: 2rem;
|
||||
width: 2.6rem;
|
||||
height: 2.6rem;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #e5e7eb;
|
||||
background: #fff;
|
||||
transform: translateX(1rem);
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
transition: transform .188s, opacity .18s;
|
||||
z-index: 9;
|
||||
|
||||
&.active {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
&>svg {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: calc(100% + 1px);
|
||||
height: calc(100% + 1px);
|
||||
|
||||
&.icon {
|
||||
width: 66%;
|
||||
height: 66%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
.vh-comment {
|
||||
box-sizing: border-box;
|
||||
padding: 2rem 1rem 1rem;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
border-radius: 0.5rem;
|
||||
background: #fff;
|
||||
box-shadow: 0 3px 8px 6px rgba(7, 17, 27, 0.05);
|
||||
overflow: hidden;
|
||||
|
||||
// 评论样式
|
||||
.twikoo {
|
||||
font-size: 0.88rem;
|
||||
|
||||
&:not(.tk-admin-container) {
|
||||
|
||||
// p标签样式
|
||||
// ul标签样式
|
||||
ul:not(.OwO-items):not(.OwO-packages),
|
||||
ol {
|
||||
margin: 0.618rem 0;
|
||||
font-size: 0.9375rem;
|
||||
line-height: 1.6;
|
||||
|
||||
li {
|
||||
color: #33333388;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
// 其下面的code标签样式
|
||||
code {
|
||||
background-color: #edeef3;
|
||||
padding: .125rem .375rem;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
}
|
||||
|
||||
ul:not(.OwO-items):not(.OwO-packages),
|
||||
ol {
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #4c4948;
|
||||
font-weight: 500;
|
||||
|
||||
a {
|
||||
box-shadow: inset 0 -.12em #60a5fa;
|
||||
transition: box-shadow .2s ease-in-out, color .2s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
box-shadow: inset 0 -1.5em #60a5fa7f
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 评论输入框Focus边框颜色
|
||||
.el-input__inner:focus,
|
||||
.el-textarea__inner:focus {
|
||||
border-color: #9999ff !important;
|
||||
}
|
||||
|
||||
// 及背景颜色
|
||||
.el-input-group__append,
|
||||
.el-input-group__prepend {
|
||||
background-color: #F5F6F8 !important;
|
||||
}
|
||||
|
||||
// 评论内容样式
|
||||
.tk-comments {
|
||||
|
||||
// 评论输入框样式
|
||||
.tk-submit {
|
||||
&>.tk-row {
|
||||
&>.tk-col {
|
||||
&>.tk-input.el-textarea {
|
||||
box-sizing: border-box;
|
||||
padding: 6px 0 26px;
|
||||
border: 1px solid #f0f0f0;
|
||||
|
||||
&>textarea {
|
||||
border: none;
|
||||
min-height: 188px !important;
|
||||
|
||||
&::placeholder {
|
||||
line-height: 1.88;
|
||||
}
|
||||
}
|
||||
|
||||
&>span {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tk-comments-container {
|
||||
.tk-comment {
|
||||
|
||||
// 作者的UI
|
||||
&.tk-master {
|
||||
&>.tk-main {
|
||||
&>.tk-row {
|
||||
&>.tk-meta {
|
||||
&>.tk-nick {
|
||||
position: relative;
|
||||
color: #9999ff;
|
||||
line-height: 1.18rem;
|
||||
|
||||
&.tk-nick-link {
|
||||
cursor: pointer;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
right: 0;
|
||||
left: auto;
|
||||
bottom: 0;
|
||||
width: 0;
|
||||
height: 1px;
|
||||
background: #9999ff;
|
||||
transition: all 0.28s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
left: 0;
|
||||
right: auto;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tk-avatar {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
box-shadow: 0px 0px 16px 0px rgba(0, 0, 0, 0.1);
|
||||
user-select: none;
|
||||
|
||||
&>img.tk-avatar-img {
|
||||
border-radius: 20%;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: -0.4rem;
|
||||
bottom: -0.4rem;
|
||||
width: 0.8rem;
|
||||
height: 0.8rem;
|
||||
background: #fff;
|
||||
background: url("/public/assets/images/admin.svg") no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-last-of-type(1) {
|
||||
.tk-main {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.tk-main {
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px dashed #ddd;
|
||||
|
||||
// 头部
|
||||
.tk-row {
|
||||
.tk-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.36rem;
|
||||
|
||||
&>.tk-nick {
|
||||
position: relative;
|
||||
color: #4c4948;
|
||||
line-height: 1.18rem;
|
||||
|
||||
&.tk-nick-link {
|
||||
cursor: pointer;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
right: 0;
|
||||
left: auto;
|
||||
bottom: 0;
|
||||
width: 0;
|
||||
height: 1px;
|
||||
background: #4c4948;
|
||||
transition: all 0.28s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
left: 0;
|
||||
right: auto;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tk-tag {
|
||||
color: #000;
|
||||
border-color: #000;
|
||||
background: transparent;
|
||||
border-radius: 2px;
|
||||
font-size: 0.72rem;
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.tk-time {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 内容
|
||||
.tk-content {
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
width: auto;
|
||||
max-height: 66px;
|
||||
vertical-align: text-bottom;
|
||||
|
||||
&.tk-owo-emotion {
|
||||
height: 28px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UA信息
|
||||
.tk-extras {
|
||||
padding-bottom: 0.28rem;
|
||||
font-size: 0.68rem;
|
||||
|
||||
.tk-extra {
|
||||
box-sizing: border-box;
|
||||
padding: 4px;
|
||||
border-radius: 0.2rem;
|
||||
background-color: #F5F6F8;
|
||||
}
|
||||
}
|
||||
|
||||
// 主体
|
||||
&>.tk-replies {
|
||||
padding-left: 1rem;
|
||||
border-left: 0.25rem solid rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 评论表情框样式
|
||||
.OwO .OwO-body {
|
||||
top: auto;
|
||||
bottom: 100%;
|
||||
|
||||
.OwO-bar {
|
||||
.OwO-packages {
|
||||
flex-direction: row;
|
||||
gap: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
.vh-copyright {
|
||||
position: relative;
|
||||
margin: 1.868rem 0;
|
||||
box-sizing: border-box;
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
border-radius: 0.618rem;
|
||||
overflow: hidden;
|
||||
|
||||
a {
|
||||
box-sizing: border-box;
|
||||
padding: 1.88px;
|
||||
position: relative;
|
||||
width: max-content;
|
||||
height: max-content;
|
||||
color: #33333388;
|
||||
box-shadow: none !important;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
content: '';
|
||||
width: 0;
|
||||
height: 2px;
|
||||
background: #60a5fa;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::before {
|
||||
width: 100%;
|
||||
left: 0;
|
||||
right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&>p {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
font-size: 0.875rem !important;
|
||||
|
||||
&>span {
|
||||
color: #9CC5FB;
|
||||
}
|
||||
}
|
||||
|
||||
&>svg {
|
||||
position: absolute;
|
||||
right: 3.66rem;
|
||||
top: 0;
|
||||
width: auto;
|
||||
height: 100%;
|
||||
opacity: 0.06;
|
||||
transform: scale(1.8);
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 666px) {
|
||||
.vh-copyright {
|
||||
&>svg {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
header.vh-header {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
box-sizing: border-box;
|
||||
padding: 0 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100vw;
|
||||
height: 3.6rem;
|
||||
background: #ffffff66;
|
||||
backdrop-filter: blur(2rem);
|
||||
box-shadow: 0 3px 8px 6px rgba(7, 17, 27, 0.05);
|
||||
z-index: 9;
|
||||
|
||||
&>section.main {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
padding: 0 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: 80%;
|
||||
max-width: 1388px;
|
||||
|
||||
a,
|
||||
span.nav-btn-item {
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
padding: 0 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
width: max-content;
|
||||
height: 80%;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
z-index: 1;
|
||||
transition: all 0.08s ease-in-out;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
&::before {
|
||||
transform: scale(1.2);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&::after {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 0.5rem;
|
||||
background: #EDEEF3;
|
||||
opacity: 0;
|
||||
transition: transform 0.16s ease-in-out, opacity 0.16s ease-in-out;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
&.search-btn {
|
||||
&>svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
&>svg {
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
&.index {
|
||||
padding-right: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
&>.link-list,
|
||||
&>.nav-btn {
|
||||
flex: 1;
|
||||
box-sizing: border-box;
|
||||
padding-right: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 0.88rem;
|
||||
height: 100%;
|
||||
width: max-content;
|
||||
max-width: 100%;
|
||||
|
||||
&:nth-last-of-type(1) {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
&>a,
|
||||
&>span {
|
||||
&.active {
|
||||
pointer-events: none;
|
||||
|
||||
&::before {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&>span {
|
||||
display: flex;
|
||||
width: 1.08rem;
|
||||
height: 1.08rem;
|
||||
|
||||
&>svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
&>.nav-btn {
|
||||
flex: none;
|
||||
width: max-content;
|
||||
|
||||
&>.menu-btn {
|
||||
display: none;
|
||||
|
||||
&>svg {
|
||||
box-sizing: border-box;
|
||||
padding: 0.36rem;
|
||||
width: 1.88rem;
|
||||
height: 1.88rem;
|
||||
border: 1px solid #EDEEF3;
|
||||
border-radius: 0.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 888px) {
|
||||
header.vh-header {
|
||||
padding: 0;
|
||||
|
||||
&>section.main {
|
||||
a {
|
||||
font-size: 0.875rem;
|
||||
|
||||
&:hover {
|
||||
&::before {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
&>svg {
|
||||
width: 1.18rem;
|
||||
height: 1.18rem;
|
||||
}
|
||||
}
|
||||
|
||||
&>.link-list {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 888px) {
|
||||
header.vh-header {
|
||||
&>section.main {
|
||||
&>.nav-btn {
|
||||
&>.menu-btn {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// main.vh-main {
|
||||
|
||||
// &>header.main-header {
|
||||
// box-sizing: border-box;
|
||||
// width: 100%;
|
||||
// height: 10rem;
|
||||
// overflow: hidden;
|
||||
// transition: all 2s ease-in-out;
|
||||
// }
|
||||
|
||||
|
||||
// &.vh-index {
|
||||
// &>header.main-header {
|
||||
// height: 20rem;
|
||||
// transition: all 2s ease-in-out;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,115 @@
|
||||
.vh-mobilesidebar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
background-color: rgba(0, 0, 0, 0.26);
|
||||
overflow: hidden;
|
||||
transition: opacity 0.16s ease-in-out, z-index 0.16s ease-in-out;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
|
||||
&>.main {
|
||||
transform: translateX(0);
|
||||
|
||||
&>.vh-mobilesidebar-list {
|
||||
&>a {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&>.main {
|
||||
box-sizing: border-box;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
max-width: 17.6rem;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
transform: translateX(-1rem);
|
||||
transition: transform 0.16s ease-in;
|
||||
overflow: hidden;
|
||||
|
||||
&>.vh-mobilesidebar-list {
|
||||
box-sizing: border-box;
|
||||
padding: 1rem 0.618rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.26rem;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
border-radius: 0.75rem;
|
||||
background: #F8F9FA;
|
||||
overflow: hidden;
|
||||
|
||||
&.user-panel {
|
||||
&>h3 {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
&>a {
|
||||
box-sizing: border-box;
|
||||
padding: 0 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.618rem;
|
||||
width: 100%;
|
||||
height: 2.25rem;
|
||||
font-size: 1rem;
|
||||
color: #66758c;
|
||||
font-weight: 600;
|
||||
border-radius: 0.375rem;
|
||||
transform: translateX(-1rem);
|
||||
overflow: hidden;
|
||||
transition: all 0.36s ease-in-out;
|
||||
|
||||
&:nth-of-type(1) {
|
||||
transition: all 0.26s ease-in-out;
|
||||
}
|
||||
|
||||
&:nth-of-type(2) {
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
color: #fff;
|
||||
background: #3366FF;
|
||||
|
||||
&>span {
|
||||
&>svg {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&>span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 0.875rem;
|
||||
height: 0.875rem;
|
||||
|
||||
&>svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
section.vh-article-pagination {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
box-sizing: border-box;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
|
||||
&>a.vh-pagination-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 0.5rem;
|
||||
background: #fff;
|
||||
box-shadow: 0 3px 8px 6px rgba(7, 17, 27, 0.05);
|
||||
font-size: 0.875rem;
|
||||
color: #99a9bf;
|
||||
user-select: none;
|
||||
transition: background 0.16s ease-in-out;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background: rgba(129, 146, 174, 0.36);
|
||||
|
||||
&>svg {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #8192AE;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
pointer-events: none;
|
||||
|
||||
&>svg {
|
||||
fill: #E8E8E8;
|
||||
}
|
||||
}
|
||||
|
||||
&>svg {
|
||||
width: 0.8rem;
|
||||
height: 0.8rem;
|
||||
fill: #99a9bf;
|
||||
transition: fill 0.16s ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
.vh-search {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
padding: 3.75rem 1rem 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
background-color: rgba(0, 0, 0, 0.26);
|
||||
overflow: hidden;
|
||||
transition: opacity 0.16s ease-in-out;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
|
||||
&>main {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&>main {
|
||||
box-sizing: border-box;
|
||||
padding: 1rem 0.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
max-width: 888px;
|
||||
height: max-content;
|
||||
max-height: 88vh;
|
||||
border-radius: 1rem;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 3px 8px 6px rgba(7, 17, 27, 0.05);
|
||||
transform: translateY(-1rem);
|
||||
transition: transform 0.16s;
|
||||
overflow: hidden;
|
||||
|
||||
|
||||
&>.search-input {
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
padding: 0.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.28rem;
|
||||
width: 100%;
|
||||
height: 2.425rem;
|
||||
background: #F6F6F6;
|
||||
border-radius: 0.66rem;
|
||||
overflow: hidden;
|
||||
|
||||
|
||||
&>svg {
|
||||
flex-shrink: 0;
|
||||
width: 1.28rem;
|
||||
height: 1.28rem;
|
||||
}
|
||||
|
||||
&>input {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
color: #9CA3AF;
|
||||
font-size: 0.875rem;
|
||||
background: transparent;
|
||||
transition: width 0.16s ease-in-out;
|
||||
|
||||
&::placeholder {
|
||||
color: #9CA3AF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&>.vh-search-list {
|
||||
margin-top: 1rem;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
background: #fff;
|
||||
user-select: none;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
|
||||
&>.vh-search-item {
|
||||
box-sizing: border-box;
|
||||
padding: 0.75rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.38rem;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
|
||||
&:hover {
|
||||
&::before {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
&>span {
|
||||
width: 100%;
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: #000000E6;
|
||||
line-height: 1.16;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&>p {
|
||||
width: 100%;
|
||||
font-size: 0.875rem;
|
||||
color: #00000080;
|
||||
font-weight: 400;
|
||||
line-height: 1.25rem;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
overflow: hidden;
|
||||
|
||||
&>span {
|
||||
color: #60a5fa;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
|
||||
declare module 'twikoo' {
|
||||
function init(options: { envId: string, el: string }): void;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
import { getCollection } from "astro:content";
|
||||
|
||||
// 格式化文章列表
|
||||
const fmtArticleList = (articleList: any) => {
|
||||
// 按年份分类
|
||||
const groupedByYear = articleList.reduce((acc: any, item: any) => {
|
||||
const year = item.data.date.getFullYear();
|
||||
// 初始化
|
||||
!acc[year] && (acc[year] = []);
|
||||
acc[year].push(item.data);
|
||||
return acc;
|
||||
}, {});
|
||||
// 转换为目标格式
|
||||
return Object.keys(groupedByYear).map(year => ({ name: parseInt(year), data: groupedByYear[year] })).reverse();
|
||||
}
|
||||
|
||||
// 获取分类下的文章列表
|
||||
const getCategoriesList = async (categories: string) => {
|
||||
const posts = await getCollection("blog");
|
||||
const articleList = posts.filter((i: any) => i.data.categories == categories).sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());;
|
||||
return fmtArticleList(articleList);
|
||||
}
|
||||
|
||||
// 获取标签下的文章列表
|
||||
const getTagsList = async (tags: string) => {
|
||||
const posts = await getCollection("blog");
|
||||
const articleList = posts.filter((i: any) => i.data.tags.includes(tags)).sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());;
|
||||
return fmtArticleList(articleList);
|
||||
}
|
||||
|
||||
// 获取归档列表
|
||||
const getArchiveList = async () => {
|
||||
const posts = await getCollection("blog");
|
||||
const articleList = posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());;
|
||||
return fmtArticleList(articleList);
|
||||
}
|
||||
|
||||
export { getCategoriesList, getTagsList, getArchiveList };
|
||||
@@ -0,0 +1,67 @@
|
||||
import path from 'path';
|
||||
import _fs from 'fs';
|
||||
const fs = _fs.promises;
|
||||
import { fileURLToPath } from 'url';
|
||||
// 获取当前模块的目录路径
|
||||
const __filename = fileURLToPath(import.meta.url); // 当前文件的绝对路径
|
||||
const __dirname = path.dirname(__filename); // 当前文件所在的目录
|
||||
|
||||
// 支持的图片扩展名
|
||||
const SUPPORTED_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp']);
|
||||
|
||||
/**
|
||||
* 获取目录中的所有图片文件
|
||||
* @param {string} dir - 图片目录路径
|
||||
* @returns {Promise<string[]>} - 图片文件名数组
|
||||
*/
|
||||
async function getImageFiles(dir: string) {
|
||||
try {
|
||||
const files = await fs.readdir(dir);
|
||||
return files.filter(file => SUPPORTED_EXTENSIONS.has(path.extname(file).toLowerCase()));
|
||||
} catch (error) {
|
||||
console.error(`无法读取目录 ${dir}:`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机打乱数组(Fisher-Yates 洗牌算法)
|
||||
* @param {any[]} array - 需要打乱的数组
|
||||
* @returns {any[]} - 打乱后的数组
|
||||
*/
|
||||
function shuffleArray(array: any[]) {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个图片名称的无限迭代器
|
||||
* @param {string} dir - 图片目录路径
|
||||
* @returns {AsyncGenerator<string>} - 异步生成器
|
||||
*/
|
||||
async function* createImageIterator(dir: string) {
|
||||
let images = await getImageFiles(dir);
|
||||
if (images.length === 0) {
|
||||
throw new Error(`目录 ${dir} 中没有图片文件`);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
images = shuffleArray([...images]); // 打乱顺序
|
||||
for (const image of images) {
|
||||
yield image;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const targetDir = path.resolve(__dirname, '../../public/assets/images/banner/'); // 目标目录
|
||||
const fileIter = createImageIterator(targetDir);
|
||||
const getCover = async (filename: string | null | undefined) => {
|
||||
if (filename) return filename;
|
||||
const { value } = await fileIter.next();
|
||||
return `/assets/images/banner/${value}`
|
||||
}
|
||||
|
||||
export default getCover;
|
||||
@@ -0,0 +1,17 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { getCollection } from "astro:content";
|
||||
// 获取封面图
|
||||
import getCover from "../utils/getCover";
|
||||
const posts = (await getCollection("blog")).sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
|
||||
// 获取文章分类
|
||||
const getCategories = () => {
|
||||
const categoriesList = posts.reduce((acc: any, i: any) => {
|
||||
acc[i.data.categories] = (acc[i.data.categories] || 0) + 1;
|
||||
return acc;
|
||||
}, {});
|
||||
return Object.entries(categoriesList).map(([title, count]) => ({ title, count }));
|
||||
}
|
||||
|
||||
// 获取文章标签
|
||||
const getTags = () => {
|
||||
const frequencyMap = new Map();
|
||||
// 统计每个元素出现的频率
|
||||
posts.forEach(i => {
|
||||
i.data.tags.forEach(tag => frequencyMap.set(tag, (frequencyMap.get(tag) || 0) + 1));
|
||||
});
|
||||
const sortedArray = [...frequencyMap.entries()].sort((a, b) => b[1] - a[1]);
|
||||
return sortedArray.slice(0, 16).map(item => item[0]);
|
||||
}
|
||||
|
||||
// 获取推荐文章 (给文章添加 recommend: true 字段)
|
||||
const getRecommendArticles = () => {
|
||||
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) }))
|
||||
};
|
||||
|
||||
export { getCategories, getTags, getRecommendArticles };
|
||||
@@ -0,0 +1,92 @@
|
||||
import dayjs from "dayjs";
|
||||
// 获取文章的描述
|
||||
const getDescription = (post: any, num: number = 150) => (post.rendered ? post.rendered.html.replace(/<[^>]+>/g, "").replace(/\s+/g, "") : post.body.replace(/\n/g, "").replace(/#/g, "")).slice(0, num) || '暂无简介'
|
||||
//处理时间
|
||||
const fmtTime = (time: any, fmt: string = 'MMMM D, YYYY') => dayjs(time).format(fmt)
|
||||
// 处理日期
|
||||
const fmtDate = (time: any) => {
|
||||
const past = dayjs(time);
|
||||
const now = dayjs();
|
||||
const duration = now.diff(past, 'seconds');
|
||||
if (duration < 60) {
|
||||
return `${duration} 秒前`;
|
||||
} else if (duration < 60 * 60) {
|
||||
const minutes = Math.floor(duration / 60);
|
||||
return `${minutes} 分钟前`;
|
||||
} else if (duration < 60 * 60 * 24) {
|
||||
const hours = Math.floor(duration / (60 * 60));
|
||||
return `${hours} 小时前`;
|
||||
} else if (duration < 60 * 60 * 24 * 30) {
|
||||
const days = Math.floor(duration / (60 * 60 * 24));
|
||||
return `${days} 天前`;
|
||||
} else if (duration < 60 * 60 * 24 * 30 * 12) {
|
||||
const months = Math.floor(duration / (60 * 60 * 24 * 30));
|
||||
return `${months} 个月前`;
|
||||
} else {
|
||||
const years = Math.floor(duration / (60 * 60 * 24 * 30 * 12));
|
||||
return `${years} 年前`;
|
||||
}
|
||||
}
|
||||
// 处理页码展示
|
||||
const fmtPage = (page: string | undefined) => page ? page.replace(/\//g, '') : null
|
||||
// 加载外部脚本
|
||||
const LoadScript = (src: string): Promise<HTMLScriptElement> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement("script");
|
||||
script.src = src;
|
||||
script.onload = () => resolve(script);
|
||||
script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
// 加载外部CSS
|
||||
const LoadStyle = (href: string): Promise<HTMLLinkElement> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const link = document.createElement("link");
|
||||
link.rel = "stylesheet";
|
||||
link.type = "text/css";
|
||||
link.href = href;
|
||||
link.onload = () => resolve(link); // CSS 加载成功
|
||||
link.onerror = () => reject(new Error(`Failed to load CSS: ${href}`)); // CSS 加载失败
|
||||
document.head.appendChild(link); // 将 <link> 添加到文档中
|
||||
});
|
||||
}
|
||||
|
||||
// 请求封装
|
||||
const $GET = async (url: string, headers: Record<string, string> = {}): Promise<any> => {
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: headers,
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP error! Status: ${res.status}`);
|
||||
}
|
||||
return res.json();
|
||||
} catch (error) {
|
||||
console.error("GET request failed:", error);
|
||||
throw error; // 抛出错误以便调用者处理
|
||||
}
|
||||
};
|
||||
|
||||
const $POST = async (url: string, data: Record<string, any>, headers: Record<string, string> = {}): Promise<any> => {
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: { ...headers, },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP error! Status: ${res.status}`);
|
||||
}
|
||||
return res.json(); // 解析 JSON 数据
|
||||
} catch (error) {
|
||||
console.error("POST request failed:", error);
|
||||
throw error; // 抛出错误以便调用者处理
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
export { $GET, $POST, getDescription, fmtTime, fmtDate, fmtPage, LoadScript, LoadStyle }
|
||||
@@ -0,0 +1,17 @@
|
||||
type RouterEventKey = "beforeCreate" | "created" | "beforeMount" | "mounted" | "afterMount";
|
||||
type RouterEventName = "astro:before-preparation" | "astro:after-preparation" | "astro:before-swap" | "astro:after-swap" | "astro:page-load";
|
||||
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 updateRouter = (key: RouterEventKey, handler: EventHandler) => {
|
||||
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);
|
||||
};
|
||||
|
||||
export default updateRouter;
|
||||
@@ -0,0 +1,30 @@
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import * as cheerio from 'cheerio';
|
||||
|
||||
const setSearchJson = async (posts: any[]) => {
|
||||
const searchIndex = posts.map(i => {
|
||||
const $ = cheerio.load(`<body>${i.rendered.html}</body>`);
|
||||
return {
|
||||
title: i.data.title,
|
||||
url: `/article/${i.data.id}`,
|
||||
content: `${i.data.title} - ` + $('body').text().replace(/\n/g, '').replace(/<[^>]+>/g, '')
|
||||
};
|
||||
});
|
||||
|
||||
try {
|
||||
await fs.writeFile(
|
||||
path.join(process.cwd(), 'dist', 'vh-search.json'),
|
||||
JSON.stringify(searchIndex)
|
||||
);
|
||||
await fs.writeFile(
|
||||
path.join(process.cwd(), 'public', 'vh-search.json'),
|
||||
JSON.stringify(searchIndex)
|
||||
);
|
||||
console.log('\x1b[32m%s\x1b[0m', '搜索文件vh-search文件已生成 successfully');
|
||||
} catch (error) {
|
||||
console.error('Error writing search index file:', error);
|
||||
}
|
||||
};
|
||||
|
||||
export default setSearchJson;
|
||||
Reference in New Issue
Block a user