diff --git a/content/about.md b/content/about.md index 6c195b7f..11bd0ca9 100644 --- a/content/about.md +++ b/content/about.md @@ -8,27 +8,33 @@ slug: "about" draft: false --- -
-
-
-

关于博主

-

Hello, World!

-
-

湖北 · 襄阳 -> 广州

-

00后,性格随和,风趣幽默

-

不活跃就是在跟生活对线~

-

工作:牛马打工人

-
-
-
-

博客构建

-

Powered by Open Source

-
    -
  • 框架:Hugo
  • -
  • 托管:Vercel / GitHub Pages
  • -
  • 源码:GitHub(heysiu/Ying)
  • -
  • 主题:二开Ying主题(自用不开源)
  • -
-
-
+ + +
+ +
02 / 这个博客 (Build)
+ +
+ +
框架Hugo · 主题 Ying
+ +
托管CNB 构建 · 又拍云 / 多吉云 / EdgeOne
+ +
评论Artalk · 自建(Cloudflare Workers)
+ +
备份CNB 主仓 + 自建 Gitea 辅仓 + 加密离线包
+ +
始于2020 年,一写六年
+ +
+ +
+Hugo +Cloudflare +Docker +Nginx +Python +Shell +
+
diff --git a/themes/Ying/assets/css/main.css b/themes/Ying/assets/css/main.css index a4d79742..6c21abaa 100644 --- a/themes/Ying/assets/css/main.css +++ b/themes/Ying/assets/css/main.css @@ -4322,6 +4322,284 @@ input[type=submit] { box-shadow: none !important; } +/* ══════════════════════════════════════════════════════════════════════════ + 关于页 · B+C 方案(时间线 + 数据一览) + ────────────────────────────────────────────────────────────────────────── + 设计约束:只使用主题既有语言——618px 容器 / zql 衬线 / 8·12px 圆角 / + .section-title 大写小标题 / 黑白灰单色。不引入任何强调色。 + 数据由 partials/about-intro.html 在构建期从 content 实时统计。 + ★ 关于页文字必须显式左对齐 —— .main 上有 text-align:center, + 靠继承会让整页居中。 + + ── 字号阶梯(★ 全节唯一字号来源,不要再写裸 rem/px)── + 之前每个元素各写各的 rem,实际算出 11.2 / 11.52 / 12.48 / 13.68 / + 13.76 / 16.8 六七种「互不相干的小字」,视觉上就是"不统一"。 + 现在收敛成 4 档 + 2 个特例,所有小字只有「正文 / 标签 / 脚注」三种: + --ab-fs-title 17px 区块内小标题(衬线) + --ab-fs-body 14px 正文(衬线) + --ab-fs-label 13px 标签 · 元信息(等宽/无衬线) + --ab-fs-micro 12px 脚注 · 注脚(等宽/无衬线) + 特例:--ab-fs-num 28px 数据大数字,--ab-fs-when 13px 时间线日期。 + ══════════════════════════════════════════════════════════════════════════ */ +.about-page { + text-align: left; + /* 字号阶梯 */ + --ab-fs-title: 1.0625rem; /* 17px 衬线小标题 */ + --ab-fs-body: 0.875rem; /* 14px 正文 */ + --ab-fs-label: 0.8125rem; /* 13px 标签/元信息 */ + --ab-fs-micro: 0.75rem; /* 12px 脚注 */ + --ab-fs-when: 0.8125rem; /* 13px 时间线日期 */ + --ab-fs-num: 1.75rem; /* 28px 数据大字 */ + /* 行高 */ + --ab-lh-tight: 1.5; + --ab-lh-body: 1.85; +} + +/* 区块间距统一在这里管:about-intro.html / about.html 里的分节 + 都用 .about-sec 包裹,不要再写内联 margin-top。 */ +.about-page .about-sec { margin-top: 44px; } +.about-page .about-sec:first-child { margin-top: 0; } +/* 小节标题(section-title)本身已有 14px 下边距,紧邻区块时不用再加。 + 全局 .section-title 是 0.9rem(14.4px),比正文(14px)还小一点点、比标签(13px)大, + 落在阶梯的缝里 —— 关于页统一压到「标签级」并加大字距,让它明显是"题头"而不是正文。 */ +.about-page .section-title { + font-size: var(--ab-fs-label); + letter-spacing: 0.18em; + margin-bottom: 18px; +} +/* 移动端收紧 */ +@media (max-width: 700px) { + .about-page .about-sec { margin-top: 34px; } +} + +.ab-attrs { margin-top: 26px; border-top: 1px solid var(--frame-color); } +[data-theme="dark"] .ab-attrs { border-top-color: var(--frame-dark-color); } +.ab-attr { + display: flex; gap: 16px; align-items: baseline; + padding: 10px 0; + border-bottom: 1px solid var(--frame-color); + font-size: var(--ab-fs-body); line-height: var(--ab-lh-body); +} +[data-theme="dark"] .ab-attr { border-bottom-color: var(--frame-dark-color); } +.ab-attr:last-child { border-bottom: 0; } +.ab-attr .k { + flex: none; width: 64px; + font-size: var(--ab-fs-label); color: var(--sanji-color); + letter-spacing: 0.08em; +} +.ab-attr .v { color: var(--text-content-color); min-width: 0; } +[data-theme="dark"] .ab-attr .v { color: var(--text-content-dark-color); } + +/* 技术栈标签:与主题 .skill-tag 同观感 */ +.ab-stack { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 14px; } +.ab-tag { + display: inline-flex; align-items: center; + padding: 3px 11px; border-radius: 999px; + border: 1px solid rgba(148, 163, 184, 0.7); + background: rgba(255, 255, 255, 0.86); + font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace; + font-size: var(--ab-fs-micro); color: #374151; + letter-spacing: 0.01em; +} +[data-theme="dark"] .ab-tag { + border-color: rgba(31, 41, 55, 0.85); + background: rgba(15, 23, 42, 0.96); + color: #e5e7eb; +} + +/* ── B 方案:时间线 ── + 时间线紧跟在属性列表后面,需要一点上方留白把它和属性区分开 */ +.ab-tl { position: relative; padding-left: 26px; margin-top: 30px; } +.ab-tl::before { + content: ''; position: absolute; + left: 5px; top: 7px; bottom: 7px; + width: 1px; background: var(--frame-color); +} +[data-theme="dark"] .ab-tl::before { background: var(--frame-dark-color); } + +.ab-item { position: relative; padding-bottom: 26px; } +.ab-item:last-child { padding-bottom: 0; } +.ab-item::before { + content: ''; position: absolute; + left: -26px; top: 6px; + width: 11px; height: 11px; border-radius: 50%; + background: var(--background-color); + border: 1.5px solid #bec8d2; + box-sizing: border-box; +} +[data-theme="dark"] .ab-item::before { + background: #0f172a; border-color: #4b5563; +} +/* 当前节点:实心,用于标出"此刻" */ +.ab-item.is-now::before { + background: #111827; border-color: #111827; +} +[data-theme="dark"] .ab-item.is-now::before { + background: #e5e7eb; border-color: #e5e7eb; +} +.ab-when { + font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace; + font-size: var(--ab-fs-when); letter-spacing: 0.06em; + color: #a8b3c0; +} +[data-theme="dark"] .ab-when { color: #6b7280; } +.ab-h { + font-family: 'zql', serif; + font-size: var(--ab-fs-title); line-height: var(--ab-lh-tight); + color: var(--text-title-color); + margin: 4px 0 6px; +} +[data-theme="dark"] .ab-h { color: var(--text-title-dark-color); } +.ab-p { + font-size: var(--ab-fs-body); line-height: var(--ab-lh-body); + color: var(--text-content-color); + margin: 0; +} +[data-theme="dark"] .ab-p { color: var(--text-content-dark-color); } +.ab-p em { font-style: normal; color: var(--sanji-color); } + +/* ── C 方案:数据一览 ── + 时间线结束 → 数据卡之间需要明显一档间距,否则和最后一条时间线粘在一起 */ +.ab-stats { + display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; + margin-top: 32px; +} +.ab-stat { + text-align: center; padding: 16px 6px; + border: 1px solid rgba(0, 0, 0, 0.08); + border-radius: 12px; +} +[data-theme="dark"] .ab-stat { border-color: rgba(255, 255, 255, 0.08); } +.ab-stat .n { + display: block; + font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace; + font-size: var(--ab-fs-num); line-height: 1.1; + color: var(--text-title-color); +} +[data-theme="dark"] .ab-stat .n { color: var(--text-title-dark-color); } +.ab-stat .l { + display: block; margin-top: 7px; + font-size: var(--ab-fs-micro); letter-spacing: 0.1em; + color: var(--sanji-color); +} + +.ab-bars { margin-top: 26px; } +.ab-bar { + display: flex; align-items: center; gap: 12px; + margin-bottom: 11px; font-size: var(--ab-fs-label); +} +.ab-bar:last-child { margin-bottom: 0; } +.ab-bar .n { + flex: none; width: 58px; + font-size: var(--ab-fs-label); color: var(--erji-color); + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} +[data-theme="dark"] .ab-bar .n { color: #9ca3af; } +.ab-bar .t { + flex: 1; height: 6px; border-radius: 99px; + background: var(--bankuai-color); overflow: hidden; +} +[data-theme="dark"] .ab-bar .t { background: rgba(31, 41, 55, 0.75); } +.ab-bar .t i { + display: block; height: 100%; border-radius: 99px; + background: #bec8d2; +} +[data-theme="dark"] .ab-bar .t i { background: #6b7280; } +.ab-bar .c { + flex: none; width: 34px; text-align: right; + font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace; + font-size: var(--ab-fs-micro); color: #a8b3c0; +} +[data-theme="dark"] .ab-bar .c { color: #6b7280; } +.ab-footnote { + font-size: var(--ab-fs-micro); line-height: 1.75; + color: #a8b3c0; + margin: 16px 0 0; +} +[data-theme="dark"] .ab-footnote { color: #6b7280; } + +/* ── 关于页响应式 ── */ +@media (max-width: 700px) { + .ab-attr { gap: 12px; } + .ab-attr .k { width: 54px; } + .ab-stats { gap: 8px; } + .ab-stat { padding: 13px 4px; } + .ab-stat .n { font-size: 1.4rem; } + .ab-bar .n { width: 48px; } +} + +/* ══════════════════════════════════════════════════════════════════════════ + 关于页 · 动效层(v7:纯 UIKit 官方写法,与其他页面完全同源,零自定义) + ────────────────────────────────────────────────────────────────────────── + 全部动画只由 UIKit scrollspy 驱动(归档页/文章页同款机制): + · 官方动画类:uk-animation-fade(0.8s 淡入)、 + uk-animation-slide-{bottom,right}-medium(0.5s / 50px 滑入) + —— 官方默认时长就在打包 CSS 里:[class*=uk-animation-]{duration:.5s}, + fade 0.8s。★ 这里不再覆盖时长/keyframes(v6 的自定义层已删, + 之前注释里说的「主题 0.2s 压制」实测并不存在)。 + · 内容组 repeat: true:移出视口重藏、滚回重播 —— 滚动时页面是活的。 + 本文件只保留两件 UIKit 管不了的事:分类条宽度填充、减弱动效守卫。 + ══════════════════════════════════════════════════════════════════════════ */ + +/* ── 1) 分类条:填充宽度从 0 长出 ── + UIKit 只管「淡入+位移」,宽度是它不管的,用 transition 补。 + 条体被 scrollspy 判定进视口后会带上 .uk-scrollspy-inview,以此放行到目标宽度; + repeat 移出视口时类被摘掉 → 宽度回到 0,下次滚进重新长出。 */ +.ab-bar .t i { + width: 0; + transition: width 0.7s cubic-bezier(0.22, 0.61, 0.36, 1) 0.15s; +} +.ab-bar.uk-scrollspy-inview .t i, +.uk-scrollspy-inview .ab-bar .t i { + width: var(--ab-bar-w, 0%); +} + +/* ── 2) 无障碍:尊重系统「减弱动态效果」── + scrollspy 默认 hidden:true,会用**内联** visibility:hidden 把未进视口的 + target 藏起来(注意是打在 target 指向的子元素上)。开了「减弱动效」的用户 + 若遇到观察器异常就会看到空白,所以这里一律放行可见 + 取消动画。 + ★ !important 必需 —— 对手是内联样式。 */ +@media (prefers-reduced-motion: reduce) { + /* ★ 必须同时覆盖「带 uk-scrollspy 的元素本身」和它的后代: + scrollspy 把内联 visibility:hidden 打在 **target 指向的元素**上。 + 当 target 就是元素自身(如 .section-title 自己写 cls:)时,它自己中招; + 当 target 是子元素(如 target: .ab-attr)时,子元素中招。 + 只写 `[uk-scrollspy] *` 会漏掉前一种情况 → 分节标题在 reduce 下空白。 + 两行都要有。 */ + .about-page [uk-scrollspy], + .about-page [uk-scrollspy] *, + .about-page .uk-animation-fade, + .about-page .uk-animation-slide-bottom-medium, + .about-page .uk-animation-slide-right-medium, + .about-page .uk-animation-slide-top-medium, + .about-page .uk-animation-slide-left-medium, + .featc-item { + visibility: visible !important; + opacity: 1 !important; + transform: none !important; + animation: none !important; + } + .ab-bar .t i { width: var(--ab-bar-w, 0%); transition: none; } +} + +/* ── 6) 数据卡悬停:极轻的边框加深,与主题其他卡片观感一致 ── + 只为「可点」之外的静态卡增加一点呼吸感,不加阴影跳变。 */ +.ab-stat { + transition: border-color 0.25s ease; +} +.ab-stat:hover { border-color: rgba(0, 0, 0, 0.16); } +[data-theme="dark"] .ab-stat:hover { border-color: rgba(255, 255, 255, 0.16); } + +/* ── 关于页响应式 ── */ +@media (max-width: 700px) { + .ab-attr { gap: 12px; } + .ab-attr .k { width: 54px; } + .ab-stats { gap: 8px; } + .ab-stat { padding: 13px 4px; } + .ab-stat .n { font-size: 1.4rem; } + .ab-bar .n { width: 48px; } +} + /* Selection Popup (Quote Button) */ #selection-popup { position: fixed; diff --git a/themes/Ying/assets/js/modules/mypjax.js b/themes/Ying/assets/js/modules/mypjax.js index c08adee1..36f33397 100644 --- a/themes/Ying/assets/js/modules/mypjax.js +++ b/themes/Ying/assets/js/modules/mypjax.js @@ -21,6 +21,32 @@ console.log("Pjax initialized:", pjax); })(); +// UIKit 重新扫描(PJAX 必需) +// ───────────────────────────────────────────────────────────── +// UIKit 只在页面加载时做一次「全量 boot」:它遍历 document.body,把带 +// uk-* 属性的元素逐个 connect 成组件。PJAX 换入的 DOM 是我们用 +// innerHTML 塞进去的,UIKit 的 MutationObserver 虽然监听了 childList, +// 但因为「换入前属性就已存在」,observer 拿不到 attribute 的新增事件, +// 加上 PJAX 的替换发生在 UIKit 首次 boot 之后,组件就永远绑不上。 +// +// 症状:uk-scrollspy 的元素停在 visibility:hidden(动画类的初始态), +// 下折叠的内容永久不可见,滚动也不会出现。 +// +// 解法:换页后手动调一次 UIkit.update()。它会从传入节点向上走到 +// document.body,再对整棵子树递归 connect —— 正好补上这次 boot。 +function reinitUIKit(root) { + if (typeof window.UIkit === 'undefined' || typeof window.UIkit.update !== 'function') return; + var target = root || document.getElementById('pjax-container') || document.body; + try { + window.UIkit.update(target); + } catch (e) { + console.warn('UIkit.update failed:', e); + } +} + +// 暴露给页面脚本:动态插入带 uk-* 属性的内容后,调一次即可让 UIKit 生效 +window.reinitUIKit = reinitUIKit; + // pjax预先加载代码 function pjax_reload() { console.log("Pjax reload triggered"); @@ -99,6 +125,12 @@ document.addEventListener("pjax:complete", function () { if (typeof NProgress !== 'undefined') NProgress.done(); pjax_reload(); + // ★ 关键:pjax_reload() 里刚给评论/说说等元素新加了 uk-scrollspy 属性, + // 此刻连同换入的整棵新 DOM 一起交给 UIKit 重新 boot一遍。 + // 必须在 pjax_reload() 之后调用,否则新加的属性会被漏掉。 + // (关于页的分节动画也走 UIKit scrollspy,所以同样靠这一步复活。) + reinitUIKit(); + // 动态加载页面特定JS(PJAX适配) // 检测当前页面是否为文章详情页,如果是则加载page-only.js var currentPath = window.location.pathname; diff --git a/themes/Ying/layouts/page/about.html b/themes/Ying/layouts/page/about.html index 13e22cbf..c5ee93aa 100644 --- a/themes/Ying/layouts/page/about.html +++ b/themes/Ying/layouts/page/about.html @@ -1,57 +1,87 @@ {{ define "main" }} -
-
- {{ .Content }} - - {{ $sectionCount := 0 }} + {{/* 关于页分节顺序:01 关于我(partial,含属性+时间线+数据) + 02 这个博客(content/about.md) + 03+ 走心评论 / 热力图 / 技能栈 / 相册,按 hugo.toml 开关动态编号。 + 间距统一由 .about-sec 管,别写内联 margin。 - - {{ if site.Params.about.enablefeatured | default true }} - {{ $sectionCount = add $sectionCount 1 }} -
-
{{ printf "%02d" $sectionCount }} / 走心评论 (Featured)
- {{ partial "featured-comments.html" . }} -
- {{ end }} + ── 动效:与归档页 archives.html 完全同源的官方写法(v7,零自定义 JS)── + 官方文档(uikit.cn/docs/animation): + 「将 .uk-animation-* 类之一添加到任何元素。当添加类时会显示动画…要在 + 其他时间点显示动画,例如当元素进入视口时,您需要使用 JavaScript + 添加该类 —— 例如,使用滚动监听组件。」 - - {{ if site.Params.about.enableheatmap }} - {{ $sectionCount = add $sectionCount 1 }} -
-
{{ printf "%02d" $sectionCount }} / 热力图 (Hot Map)
- {{ partial "heatmap.html" . }} -
- {{ end }} + 四条要点(全部页面既有范式,本页照办): + ① scrollspy 挂在被包裹的容器上,一个容器托管一组元素。 + ② target 用后代选择器(.ab-attr),不写 `>` —— 写 `>` 只匹配直接 + 子元素,结构一变就一个都选不中(v4 动画全灭的根因)。 + ③ 动画类一律官方:uk-animation-fade(标题淡入 0.8s)、 + uk-animation-slide-{bottom,right}-medium(内容 50px 滑入 0.5s)。 + ★ 不要用 -small:位移只有 10px,在这么宽的版面里根本看不出来。 + ④ 内容组一律 repeat: true —— 移出视口重藏、滚回重播(归档页年份 + 标题同款),页面滚动时才是「活的」;分节标题不 repeat(同归档 h3)。 - - {{ if site.Params.about.enableskills }} - {{ $sectionCount = add $sectionCount 1 }} -
-
{{ printf "%02d" $sectionCount }} / 技能栈 (System Ops)
- {{ partial "skills.html" . }} -
- {{ end }} - - - {{ if site.Params.about.enablealbum }} - {{ $sectionCount = add $sectionCount 1 }} -
-
{{ printf "%02d" $sectionCount }} / 光影 (Album)
-
- {{ range site.Data.album }} -
-
- - {{ if .date }}{{ .date }}{{ end }} -
- {{ .title }} + PJAX:换入的 DOM 由 mypjax.js 的 reinitUIKit() 重新 boot(UIKit 的 + MutationObserver 只反应属性新增,管不了 PJAX 整树换入)。 + 异步渲染的内容(走心评论卡片)渲染完要自己调 UIkit.update()。 */}} + +
+
+
+
01 / 关于我 (Timeline)
+ {{ partial "about-intro.html" . }} +
+ + {{ .Content }} + + {{ $sectionCount := 2 }} + + + {{ if site.Params.about.enablefeatured | default true }} + {{ $sectionCount = add $sectionCount 1 }} +
+
{{ printf "%02d" $sectionCount }} / 走心评论 (Featured)
+ {{ partial "featured-comments.html" . }} +
+ {{ end }} + + + {{ if site.Params.about.enableheatmap }} + {{ $sectionCount = add $sectionCount 1 }} +
+
{{ printf "%02d" $sectionCount }} / 热力图 (Hot Map)
+ {{ partial "heatmap.html" . }} +
+ {{ end }} + + + {{ if site.Params.about.enableskills }} + {{ $sectionCount = add $sectionCount 1 }} +
+
{{ printf "%02d" $sectionCount }} / 技能栈 (System Ops)
+ {{ partial "skills.html" . }} +
+ {{ end }} + + + {{ if site.Params.about.enablealbum }} + {{ $sectionCount = add $sectionCount 1 }} +
+
{{ printf "%02d" $sectionCount }} / 光影 (Album)
+
+ {{ range site.Data.album }} +
+
+ + {{ if .date }}{{ .date }}{{ end }} +
+ {{ .title }} +
+ {{ end }}
- {{ end }}
+ {{ end }} +
- {{ end }} -
-
-{{ end }} \ No newline at end of file +{{ end }} diff --git a/themes/Ying/layouts/partials/about-intro.html b/themes/Ying/layouts/partials/about-intro.html new file mode 100644 index 00000000..fbeef2ac --- /dev/null +++ b/themes/Ying/layouts/partials/about-intro.html @@ -0,0 +1,110 @@ +{{/* 关于页 · B+C 方案:时间线(B) + 数据一览(C) + ──────────────────────────────────────────────────────────────── + 数据全部在构建期从 content/ 实时统计,不依赖任何外部接口: + · 篇数 / 年数 / 站龄 → .Site.RegularPages 与最早文章日期 + · 分类占比 → .Site.Taxonomies.categories + · 热力图 → 由 heatmap.html 提供(本 partial 不重复渲染) + ★ 时间线节点写在下方 $nodes 里,改文案只动那一处。 + ──────────────────────────────────────────────────────────────── */}} + +{{- $posts := where .Site.RegularPages "Type" "posts" -}} + +{{/* ── 统计:总篇数 ── */}} +{{- $total := len $posts -}} + +{{/* ── 统计:首篇日期与年数 ── + 用最早的一篇(ByDate 升序后的 index 0)作为起点,算站龄。 */}} +{{- $ordered := $posts.ByDate -}} +{{- $first := index $ordered 0 -}} +{{- $firstYear := $first.Date.Format "2006" -}} +{{- $nowYear := now.Format "2006" -}} +{{- $spanYears := sub (int $nowYear) (int $firstYear) -}} + +{{/* ── 统计:今年更新篇数(当年发文数) ── */}} +{{- $thisYear := len (where $posts "Date.Year" (int $nowYear)) -}} + +{{/* ── 统计:分类占比(取前 5,按篇数降序) ── */}} +{{- $cats := slice -}} +{{- with .Site.Taxonomies.categories -}} + {{- range . -}} + {{- $cats = $cats | append (dict "name" .Page.LinkTitle "count" (len .Pages)) -}} + {{- end -}} +{{- end -}} +{{- $cats = sort $cats "count" "desc" -}} +{{- $top := first 5 $cats -}} +{{- $maxC := 1 -}} +{{- with $top }}{{ $maxC = (index . 0).count }}{{ end -}} + +{{/* ── 时间线节点 ── + 想改文案/加节点,只改这个数组即可。isNow = true 的节点会画成实心圆点。 */}} +{{- $nodes := slice + (dict "when" "2020 · 起点" + "title" "在襄阳,给博客开了个头" + "body" "高考完的那个夏天,怀揣着对互联网的好奇搭了这个小站。第一篇文章还留在归档里。" + "isNow" false) + (dict "when" "2021 — 2024 · 折腾" + "title" "换主题、磨 CSS、把 Typecho 拆了个遍" + "body" "那时候不懂代码,到处找教程,把好好的主题改得面目全非,但乐在其中。" + "isNow" false) + (dict "when" "2025 · 做减法" + "title" "从 Typecho 搬到 Hugo" + "body" "文章多了才明白,博客的本质是内容,不是花哨的外表。" + "isNow" false) + (dict "when" "此刻 · 在广州" + "title" "毕业两年,深漂广漂中" + "body" "不活跃就是在跟生活对线。想去南京落脚,先把手里的事做好。" + "isNow" true) +-}} + + +
+
坐标湖北 · 襄阳 → 广州
+
其人00 后,性格随和,风趣幽默
+
打算未来计划定居江苏南京
+
近况深漂广漂中,不活跃就是在跟生活对线
+
+ + +
+ {{- range $nodes }} +
+
{{ .when }}
+

{{ .title }}

+

{{ .body }}

+
+ {{- end }} +
+ + +
+
{{ $total }}篇文章
+
{{ $spanYears }}年积累
+
{{ $thisYear }}今年更新
+
+ +{{- if $top }} + +
+ {{- range $top }} + {{- $pct := div (mul .count 100) $maxC }} +
+ {{ .name }} + + {{ .count }} +
+ {{- end }} +
+

+ 首篇发布于 {{ $first.Date.Format "2006 年 1 月" }} · 以上统计在构建时实时生成 +

+{{- end }} + diff --git a/themes/Ying/layouts/partials/featured-comments.html b/themes/Ying/layouts/partials/featured-comments.html index d7e05254..3f56d4bd 100644 --- a/themes/Ying/layouts/partials/featured-comments.html +++ b/themes/Ying/layouts/partials/featured-comments.html @@ -5,14 +5,14 @@ -
+
走心评论

当我看到各位在不同的时间和故事中,发出一份份走心的评论时,一份温暖之意即笼罩在我的心头。

+
-
加载中…
+ + + +
@@ -106,7 +189,7 @@ var avatar = 'https://weavatar.com/avatar/' + esc(c.email_encrypted) + '?d=mp&s=120'; var nick = esc(c.nick); html += - '' + + '' + '
' + '' + '' + nick + '' + @@ -117,9 +200,47 @@ ''; } grid.innerHTML = html; + + // ★ 卡片是 fetch 回来后才插进 DOM 的,UIKit 的 MutationObserver 捕捉不到 + // (它只对「属性新增」反应,不管新插入的子树)—— 必须手动 boot 一次。 + // 之后完全交给 scrollspy(repeat: true): + // · 这一节在视口内 → 同一批 target 进同一个 update 周期, + // delay 依次累加,自动错落浮现; + // · 在视口外 → 先藏住,滚到时逐条入场,滚走再滚回会重播。 + if (typeof window.UIkit !== 'undefined' && typeof window.UIkit.update === 'function') { + try { window.UIkit.update(grid); } catch (e) {} + } } - function load() { + // 失败/超时提示:明确告知并给一次重试,比一直挂着骨架屏好 + function renderRetry(msg) { + grid.innerHTML = + '
' + esc(msg) + '
' + + '
'; + var btn = document.getElementById('featc-retry-btn'); + if (btn) btn.addEventListener('click', function () { + grid.innerHTML = '
'; + load(true); + }); + } + + var CACHE_KEY = 'featc_cache_v1'; + var CACHE_TTL = 10 * 60 * 1000; // 10 分钟 + + function readCache() { + try { + var raw = sessionStorage.getItem(CACHE_KEY); + if (!raw) return null; + var o = JSON.parse(raw); + if (!o || !o.t || (Date.now() - o.t) > CACHE_TTL) return null; + return o.list; + } catch (e) { return null; } + } + function writeCache(list) { + try { sessionStorage.setItem(CACHE_KEY, JSON.stringify({ t: Date.now(), list: list })); } catch (e) {} + } + + function load(force) { // 排版预览 mock(?featmock=1):接口未部署 / 无精选数据时也能看样式 if (/[?&]featmock=1/.test(location.search)) { render([ @@ -133,11 +254,33 @@ ]); return; } - fetch(API) + + // 会话内缓存:先进关于页已经拉过一次,再进(含 PJAX 来回)直接命中, + // 不用再等那 1 秒多。TTL 10 分钟,兼顾新鲜度。 + if (!force) { + var cached = readCache(); + if (cached) { render(cached); return; } + } + + // ★ 超时兜底:接口实测 1–1.5s,但偶有长尾。 + // 8s 还没回就不再干等,切成「重试」提示 —— 比无限挂骨架屏诚实。 + var ctrl = ('AbortController' in window) ? new AbortController() : null; + var timer = setTimeout(function () { if (ctrl) ctrl.abort(); }, 8000); + + fetch(API, ctrl ? { signal: ctrl.signal } : undefined) .then(function (r) { return r.ok ? r.json() : Promise.reject(r.status); }) - .then(function (data) { render((data && data.comments) || []); }) - .catch(function () { - if (grid) grid.innerHTML = '
还没有走心评论,去和博主聊聊吧~
'; + .then(function (data) { + clearTimeout(timer); + var list = (data && data.comments) || []; + writeCache(list); + render(list); + }) + .catch(function (err) { + clearTimeout(timer); + // 有缓存就先用缓存顶(宁可显示旧数据也别显示错误) + var stale = readCache(); + if (stale) { render(stale); return; } + renderRetry('评论暂时没加载出来'); }); } diff --git a/themes/Ying/layouts/partials/heatmap.html b/themes/Ying/layouts/partials/heatmap.html index ae89ebe8..979ad7ee 100644 --- a/themes/Ying/layouts/partials/heatmap.html +++ b/themes/Ying/layouts/partials/heatmap.html @@ -11,9 +11,14 @@ } -
+
+{{/* ★ 动效说明:散点图画在 #heatmap 里,那行内联 `visibility:visible!important` + 是 ECharts 测量容器尺寸所需,不能被 scrollspy 的 hidden 覆盖。 + 所以 scrollspy 挂在**外层 wrapper** 上(用 cls:,target 默认为自身), + 官方 slide-bottom-medium(50px 上浮)入场;图本身不受影响。 + 不开 repeat —— 单个大块反复重播观感太跳。 */}}