Skip to content

Commit 4021ae5

Browse files
committed
feat: 新增LiquidGlass组件文档和RecentComments组件
style(blog): 调整Card组件内边距和标题行数 style(common): 修改EntryHeader组件渐变遮罩透明度 refactor(pages): 增加RecentComments组件并调整首页布局 docs: 添加LiquidGlass组件使用说明文档 docs: 新增组件标签目录说明文档
1 parent 0ded377 commit 4021ae5

6 files changed

Lines changed: 719 additions & 6 deletions

File tree

src/components/blog/Card.astro

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,10 @@ const entryDescription =
6969
</div>
7070

7171
<!-- 内容区域 -->
72-
<div class="p-6">
72+
<div class="p-5">
7373
<!-- 文章标题 -->
7474
<h4
75-
class="mb-2 text-lg font-bold text-txt-p dark:text-darkmode-txt-p line-clamp-2 leading-tight"
75+
class="mb-2 text-lg font-bold text-txt-p dark:text-darkmode-txt-p line-clamp-1 leading-tight"
7676
>
7777
<a
7878
href={`/blog/${entry.id}`}

src/components/common/EntryHeader.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ tags?.sort((a: string, b: string) => a.localeCompare(b));
100100
loading="eager"
101101
/>
102102
{/* 渐变遮罩层,确保文字可读性 */}
103-
<div class="absolute inset-0 bg-gradient-to-t from-black/70 via-black/30 to-transparent" />
103+
<div class="absolute inset-0 bg-gradient-to-t from-black/50 via-black/10 to-black/0" />
104104
</div>
105105
)}
106106

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
---
2+
import LiquidGlassLess from "@components/common/LiquidGlassLess.astro";
3+
import { getEntries } from "@lib/contentParser";
4+
import type { BlogEntry, PagesEntry } from "@/types";
5+
6+
const { limit = 8 } = Astro.props as { limit?: number };
7+
const allPosts = (await getEntries("blog")) as BlogEntry[];
8+
const pages = (await getEntries("pages")) as PagesEntry[];
9+
const titleMap = Object.fromEntries(
10+
[...allPosts,...pages].map((p) => [p.id, p.data.title])
11+
);
12+
---
13+
14+
<section class="rounded-lg pt-3">
15+
<div class="flex items-center justify-between mb-4 px-3">
16+
<h3 class="text-xl font-bold text-txt-p dark:text-darkmode-txt-p">
17+
最近留言
18+
</h3>
19+
</div>
20+
<ul id="recent-comments-list" class="px-2 md:px-3 space-y-1"></ul>
21+
</section>
22+
23+
<script define:vars={{
24+
twikooEnvId: import.meta.env.PUBLIC_TWIKOO_ENV_ID || "",
25+
limit,
26+
titleMap
27+
}}>
28+
const list = document.getElementById("recent-comments-list");
29+
const maxLen=100;
30+
function showLoading(){
31+
if(!list) return;
32+
list.setAttribute('aria-busy','true');
33+
list.innerHTML = `
34+
<li class="py-4">
35+
<div class="flex items-center justify-center gap-2 text-slate-500 dark:text-slate-200">
36+
<span class="inline-block w-5 h-5 border-2 border-slate-400 border-t-transparent rounded-full animate-spin"></span>
37+
<span>正在加载留言…</span>
38+
</div>
39+
</li>
40+
`;
41+
}
42+
function showError(msg){
43+
if(!list) return;
44+
list.removeAttribute('aria-busy');
45+
list.innerHTML = `
46+
<li class="py-4">
47+
<div class="text-center text-red-600 dark:text-red-400">
48+
${msg || "评论获取失败,请稍后重试。"}
49+
<button id="rc-retry-btn" class="ml-2 px-2 py-1 text-sm rounded border border-red-300 dark:border-red-700 bg-red-50 hover:bg-red-100 dark:bg-red-900/20">重试</button>
50+
</div>
51+
</li>
52+
`;
53+
const btn = document.getElementById("rc-retry-btn");
54+
if(btn){ btn.addEventListener("click", load, { once: true }); }
55+
}
56+
function stripHtml(s){
57+
try{
58+
return (s||"").replace(/<[^>]*>/g,"").trim()
59+
}catch(e){
60+
console.error('stripHtml报错:',e)
61+
return "";
62+
}
63+
}
64+
function pathFromUrl(u){
65+
try{
66+
//根据/截取路径,获取最后一个路径段
67+
if(u.indexOf("/")!=-1){
68+
u=u.split("/")[u.split("/").length-1];
69+
}
70+
return decodeURIComponent(u)||u
71+
}catch(e){
72+
console.error('格式化路径报错:',e)
73+
return u;
74+
}
75+
}
76+
function ensureTwikoo(cb){
77+
if(window.twikoo){cb();return}
78+
const s=document.createElement("script");
79+
s.src="https://cdn.jsdelivr.net/npm/twikoo@1.6.44/dist/twikoo.min.js";
80+
s.async=true;
81+
s.onload=cb;
82+
document.body.appendChild(s);
83+
}
84+
function render(items){
85+
if(list){ list.removeAttribute('aria-busy'); }
86+
if(!list || !items || items.length==0) {
87+
//显示一个空提示
88+
list.innerHTML=`
89+
<li class="text-slate-500 dark:text-slate-200 text-center">还没有留言哦</li>
90+
`;
91+
return;
92+
}
93+
list.innerHTML="";
94+
items.forEach((it,index)=>{
95+
const url=it.url||it.link||"";
96+
const path=pathFromUrl(url);
97+
const title=titleMap[path]||path;
98+
const full=stripHtml(it.comment||it.content||"");
99+
const short=full.length>maxLen?full.slice(0,maxLen)+"…":full;
100+
const li=document.createElement("li");
101+
li.className="group rounded-[25px] hover:bg-primary/5 transition-all duration-300 intersect:animate-fadeUp opacity-0 p-2 hover:bg-orange-500 dark:hover:bg-orange-500 hover:text-darkmode-txt-light [&_*]:hover:text-darkmode-txt-p";
102+
li.style.animationDelay=`${index * 200}ms`;
103+
li.innerHTML=`
104+
<a class="no-underline hover:text-primary" href="${url}">
105+
<div class="text-txt-light dark:text-darkmode-txt-light flex items-center gap-2 mt-1 h-8">
106+
<span class="glass w-9 h-9 rounded-full overflow-hidden hover:rotate-12 duration-300">
107+
<img src="${it.avatar||it.avatarUrl||""}" alt="${it.nick||it.nickname||"匿名"}" class="w-9 h-9 rounded-full">
108+
</span>
109+
<div class="flex flex-col justify-center leading-tight h-8">
110+
<span class="text-base text-txt-p dark:text-darkmode-txt-p">${it.nick||"匿名"}</span>
111+
<span class="text-xs text-slate-500 dark:text-slate-200">${new Date(it.created||Date.now()).toLocaleString()}</span>
112+
</div>
113+
</div>
114+
<div class="text-[#3e5f6a] dark:text-darkmode-txt-p leading-7 line-clamp-2 font-semibold mt-3" title="${full.replace(/"/g,'&quot;')}">${short}</div>
115+
<div class="text-sm italic opacity-0 group-hover:opacity-100 text-slate-600 dark:text-slate-100/50 border-t border-slate-300/30 dark:border-darkmode-border/30 pt-1 text-right transition-opacity duration-300">
116+
来自:${title}
117+
</div>
118+
</a>
119+
`;
120+
list.appendChild(li);
121+
});
122+
}
123+
function load(){
124+
if(!list){return}
125+
if(!twikooEnvId){
126+
showError("未配置 Twikoo 环境 ID");
127+
return;
128+
}
129+
showLoading();
130+
ensureTwikoo(()=>{
131+
if(!window.twikoo){
132+
showError("评论系统初始化失败");
133+
return;
134+
}
135+
window.twikoo.getRecentComments({ envId: twikooEnvId, pageSize: limit })
136+
.then((res)=>{
137+
const arr=res?.comments||res||[];
138+
console.log('从twikoo获取评论列表',arr)
139+
render(arr);
140+
})
141+
.catch((err)=>{
142+
console.error('获取最近评论失败', err);
143+
showError("评论获取失败,请稍后重试。");
144+
});
145+
});
146+
}
147+
// document.addEventListener("DOMContentLoaded", load);
148+
document.addEventListener("astro:page-load", load);
149+
function ensureTwikoo(cb){
150+
if(window.twikoo){cb();return}
151+
const s=document.createElement("script");
152+
s.src="https://cdn.jsdelivr.net/npm/twikoo@1.6.44/dist/twikoo.min.js";
153+
s.async=true;
154+
s.onload=cb;
155+
s.onerror=()=>{
156+
showError("评论系统加载失败,请稍后重试。");
157+
};
158+
document.body.appendChild(s);
159+
}
160+
</script>
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
---
2+
title: LiquidGlass 组件使用说明
3+
description: 介绍 LiquidGlass 组件的功能、使用方法与相关代码。
4+
createdAt: 2024-01-01
5+
published: true
6+
categories:
7+
- 使用说明
8+
author: Maple
9+
image: "@assets/uploads/demo.jpg"
10+
tags:
11+
- 文章
12+
- 示例
13+
draft: false
14+
hideToc: false
15+
---
16+
17+
# LiquidGlass 组件使用说明
18+
19+
## 功能简介
20+
21+
- 本组件参考开源项目 [liquid-glass-effect-macos](https://github.com/lucasromerodb/liquid-glass-effect-macos) 实现。
22+
- 提供「液态玻璃」视觉效果的轻量组件,主要使用 `backdrop-filter` 与多层叠加(effect/tint/shine)。
23+
- 当前项目使用的是 Astro 组件:`LiquidGlassLess.astro`(轻量版),可通过 `heavy` 切换更强的模糊/色调;通过 `enableGlassEffect` 快速降级为扁平化样式。
24+
- 可用于卡片、侧栏、按钮、段落容器等场景,支持入场动画(`fadeUp`/`fadeLeft`/`fadeRight`/`fadeDown`)。
25+
- 兼容深色模式;无数据库或后端依赖,纯前端样式组件。
26+
27+
## 涉及代码
28+
29+
- 组件文件:
30+
- `src/components/common/LiquidGlassLess.astro`
31+
- (可选)纯容器版本:`src/components/common/LiquidGlassNone.astro`
32+
- 样式文件:
33+
- `src/styles/glass-new.scss`(液态玻璃核心样式,包含 `liquidGlass-effect(-h)``liquidGlass-tint(-h)``liquidGlass-shine` 等)
34+
- `src/styles/glass.scss`(标准毛玻璃基类 `glass` 与常用变体 `dock`/`button`
35+
- 通过 `src/styles/main.scss` 中的 `@include meta.load-css("glass")``@include meta.load-css("glass-new")` 统一加载
36+
- 常见引用位置(示例):
37+
- `src/components/about/EntryLayout.astro`
38+
- `src/components/terms/EntryLayout.astro`
39+
- `src/components/blog/EntryLayout.astro`
40+
- `src/pages/index.astro``src/pages/page/[slug].astro`
41+
- 组件主要 Props(以 `LiquidGlassLess.astro` 为准):
42+
- `containerType`: `'div' | 'section' | 'nav' | 'header' | 'footer'`(默认 `div`
43+
- `containerClass`: 外层容器类名(默认空)
44+
- `wrapperClass`: 包装器类名(默认 `dock`
45+
- `textClass`: 内容区类名(默认空)
46+
- `animation`: `'fadeLeft' | 'fadeRight' | 'fadeUp' | 'fadeDown' | 'none'`(默认 `fadeUp`
47+
- `heavy`: `boolean`,启用更强效果(应用 `-h` 变体)
48+
- `enableGlassEffect`: `boolean`,关闭后使用扁平化样式(`bg + border + shadow`
49+
- `effectClass`/`tintClass`/`shineClass`: 为叠加层追加类名(如 `rounded-[25px]``ring-2 ring-white/20`
50+
51+
## 使用方法
52+
53+
- 基本引入与用法(`.astro` 文件):
54+
```astro
55+
---
56+
import LiquidGlassLess from "@components/common/LiquidGlassLess.astro";
57+
---
58+
59+
<LiquidGlassLess
60+
containerType="div"
61+
containerClass="my-6"
62+
wrapperClass="dock p-6"
63+
textClass=""
64+
animation="fadeUp"
65+
heavy
66+
>
67+
<h3 class="mb-2">毛玻璃标题</h3>
68+
<p class="text-txt-light dark:text-darkmode-txt-light">内容区域</p>
69+
</LiquidGlassLess>
70+
```
71+
72+
- 关闭玻璃效果(扁平化降级):
73+
```astro
74+
<LiquidGlassLess
75+
wrapperClass="dock !p-6"
76+
containerClass="py-5"
77+
enableGlassEffect={false}
78+
>
79+
<p>在不支持 `backdrop-filter` 或需要更高性能时使用</p>
80+
</LiquidGlassLess>
81+
```
82+
注:已增加全局控制配置项:在 `src/lib/config.ts` 中设置 `ENABLE_GLASS_EFFECT`,控制所有组件的玻璃效果的默认状态。如果某个组件单独设置了`enableGlassEffect`, 则会覆盖默认状态。
83+
84+
85+
- 自定义叠加层与圆角:
86+
```astro
87+
<LiquidGlassLess
88+
wrapperClass="dock"
89+
effectClass="rounded-[25px]"
90+
tintClass="rounded-[25px]"
91+
shineClass="rounded-[25px]"
92+
>
93+
<div class="p-6">统一圆角以避免溢出裁剪</div>
94+
</LiquidGlassLess>
95+
```
96+
97+
- 动画控制:
98+
```astro
99+
<LiquidGlassLess animation="fadeLeft">
100+
<div class="p-4">入场动画使用 `intersect:` 前缀自动触发</div>
101+
</LiquidGlassLess>
102+
```
103+
104+
- 使用建议:
105+
- 列表或批量卡片场景优先使用 `LiquidGlassLess`,必要时打开 `heavy` 增强质感。
106+
- 需要稳定性能或在老旧浏览器中建议 `enableGlassEffect={false}`
107+
- 叠加层圆角应与内容区一致,避免视觉裁切;可通过 `rounded-*` 类统一设置。

0 commit comments

Comments
 (0)