fix(polish): 视觉复核二轮六项——网格单一高度源/导航i18n/热力图缓存等

- 图库:li 去 aspect-ratio(与行跨度双高度约束竞争=列洞根因面),
  img 改 object-fit cover 填满;截图前整页滚动触发懒加载
- Header:导航标签按 locale 取字典(en 页曾是中文);图标钮暗色
  提色去底块(视觉复核 02/12 的灰块)
- 热力图:构建期模块级缓存,zh/en 两页共享一次抓取(en 页曾空格)
- en 首页移除机器翻译横幅(文章级标注才是本意);面板居中;计数器
  文字投影
- Artalk E2E 报 fetch 失败定性为测试环境跨域(127.0.0.1 页面拉
  wait4x.online API),生产同源不受影响——上线后专项验证

验证: pnpm astro build 21 页 exit 0;node tests/e2e.mjs 30/30 PASS
      (新构建全链重跑,含滚动后 21/21 图库断言)

Spec: 2026-10-25_fix-site-polish

Co-authored-by: GLM-5.3
This commit is contained in:
wait4xandGLM-5.3 committed 2026-10-25 10:57:16 +08:00
1 parent 7a2880b65d
commit e21fac7e1d
6 files changed
+36 -9

No files matched your search

+1
View File
@@ -276,6 +276,7 @@ const navItems = [
<style> <style>
.search-dialog { .search-dialog {
margin: 12vh auto auto;
margin-top: 12vh; margin-top: 12vh;
width: min(560px, calc(100vw - 32px)); width: min(560px, calc(100vw - 32px));
border: 1px solid var(--line); border: 1px solid var(--line);
+9 -5
View File
@@ -1,18 +1,19 @@
--- ---
import { localeOf, localePath } from '../lib/locale'; import { localeOf, localePath } from '../lib/locale';
import { t } from '../i18n/ui';
// 顶部导航:≤4 项纪律(防 higan 十项复发);当前页 aria-current // 顶部导航:≤4 项纪律(防 higan 十项复发);当前页 aria-current
const nav = [
{ href: '/archives/', label: '文章' },
{ href: '/gallery/', label: '图库' },
{ href: '/about/', label: '关于' },
];
const path = Astro.url.pathname; const path = Astro.url.pathname;
const isCurrent = (href: string) => const isCurrent = (href: string) =>
path === href || path === href.replace(/\/$/, '') || path.startsWith(href); path === href || path === href.replace(/\/$/, '') || path.startsWith(href);
// 语言切换:同路径跨前缀跳转(zh 无前缀 ↔ /en/),预构建双语毫秒级 // 语言切换:同路径跨前缀跳转(zh 无前缀 ↔ /en/),预构建双语毫秒级
const curLocale = localeOf(path); const curLocale = localeOf(path);
const nav = [
{ href: '/archives/', label: t('nav_posts', curLocale) },
{ href: '/gallery/', label: t('nav_gallery', curLocale) },
{ href: '/about/', label: t('nav_about', curLocale) },
];
const switchHref = localePath(path, curLocale === 'zh' ? 'en' : 'zh'); const switchHref = localePath(path, curLocale === 'zh' ? 'en' : 'zh');
const switchLabel = curLocale === 'zh' ? 'EN' : '中'; const switchLabel = curLocale === 'zh' ? 'EN' : '中';
--- ---
@@ -114,6 +115,9 @@ const switchLabel = curLocale === 'zh' ? 'EN' : '中';
.nav-link:hover { color: var(--ink-1); } .nav-link:hover { color: var(--ink-1); }
.nav-link[aria-current='page'] { color: var(--accent); } .nav-link[aria-current='page'] { color: var(--accent); }
.nav-lang { font-family: var(--font-mono); font-size: 13px; } .nav-lang { font-family: var(--font-mono); font-size: 13px; }
/* 暗色下图标钮发灰难辨(视觉复核 02/12):图标链接统一提一档并去底块感 */
.nav-link:has(svg) { color: var(--ink-2); background: transparent; }
.nav-link:has(svg):hover { color: var(--ink-1); }
@media (max-width: 560px) { @media (max-width: 560px) {
.nav-link { font-size: 14px; padding-inline: var(--space-1); } .nav-link { font-size: 14px; padding-inline: var(--space-1); }
} }
+9 -1
View File
@@ -86,7 +86,7 @@ const first = cards[0];
<li <li
class="shot" class="shot"
data-group={card.group} data-group={card.group}
style={`aspect-ratio:${card.w}/${card.h}; --s3:${card.s3}; --s2:${card.s2}`} style={`--s3:${card.s3}; --s2:${card.s2}`}
> >
<button <button
type="button" type="button"
@@ -279,6 +279,13 @@ const first = cards[0];
border-radius: 10px; border-radius: 10px;
overflow: hidden; overflow: hidden;
} }
/* 高度唯一来源=行跨度;图片封面式填满格子——消除 aspect-ratio 与
span 的双约束竞争(视觉复核发现的列洞/丢图根因面) */
.shot img {
width: 100%;
height: 100%;
object-fit: cover;
}
@media (min-width: 820px) { @media (min-width: 820px) {
.shot { grid-row-end: span var(--s3, 12); } .shot { grid-row-end: span var(--s3, 12); }
} }
@@ -365,6 +372,7 @@ const first = cards[0];
flex-direction: column; flex-direction: column;
gap: 2px; gap: 2px;
} }
.lb-meta { text-shadow: 0 1px 4px rgba(0, 0, 0, 0.6); }
.lb-meta strong { .lb-meta strong {
color: #fff; color: #fff;
font-size: 13px; font-size: 13px;
+10 -1
View File
@@ -41,7 +41,7 @@ function toDateString(tsSec: number): string {
* 聚合原始贡献点为 53 周年历:起点为「今天所在周的周一」前推 52 周, * 聚合原始贡献点为 53 周年历:起点为「今天所在周的周一」前推 52 周,
* 网格尾部未来的日子填 0(格渲染为空档),首列可能含早于窗口的 0 天。 * 网格尾部未来的日子填 0(格渲染为空档),首列可能含早于窗口的 0 天。
*/ */
export async function loadHeatmap(): Promise<HeatmapYear> { async function loadHeatmapInternal(): Promise<HeatmapYear> {
let raw: RawHeat[] = []; let raw: RawHeat[] = [];
try { try {
const res = await fetch(HEATMAP_API, { const res = await fetch(HEATMAP_API, {
@@ -81,3 +81,12 @@ export async function loadHeatmap(): Promise<HeatmapYear> {
} }
return { weeks, total }; return { weeks, total };
} }
// 构建期模块级缓存:zh/en 两个首页各渲染一次,若各自直连远端会抓两遍;
// 缓存后第二个页面复用同一份结果(视觉复核发现 en 页数据偶发为空)
let _cache: Promise<HeatmapYear> | null = null;
export function loadHeatmap(): Promise<HeatmapYear> {
_cache ??= loadHeatmapInternal();
return _cache;
}
-2
View File
@@ -2,7 +2,6 @@
// /en/ 首页:英文 UI + 双语内容(postsEn 优先,缺则中文回退) // /en/ 首页:英文 UI + 双语内容(postsEn 优先,缺则中文回退)
import { getCollection } from 'astro:content'; import { getCollection } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro'; import BaseLayout from '../../layouts/BaseLayout.astro';
import AiTranslatedBadge from '../../components/AiTranslatedBadge.astro';
import { t } from '../../i18n/ui'; import { t } from '../../i18n/ui';
import Heatmap from '../../components/geek/Heatmap.astro'; import Heatmap from '../../components/geek/Heatmap.astro';
import { POST_TRACKS, trackOf } from '../../content.config'; import { POST_TRACKS, trackOf } from '../../content.config';
@@ -20,7 +19,6 @@ const t9 = (cat: keyof typeof POST_TRACKS) => trackOf(cat);
--- ---
<BaseLayout title="wait4x" description="wait4x's blog: reading · code · photography" track="tech"> <BaseLayout title="wait4x" description="wait4x's blog: reading · code · photography" track="tech">
<AiTranslatedBadge />
<h1 class="visually-hidden">wait4x</h1> <h1 class="visually-hidden">wait4x</h1>
{years.map((year) => ( {years.map((year) => (
<section class="year"> <section class="year">
+7
View File
@@ -46,6 +46,7 @@ await page.keyboard.press('t');
const byKey = await page.getAttribute('html', 'data-theme'); const byKey = await page.getAttribute('html', 'data-theme');
ok('明暗切换·t 键', byKey !== persisted, `t 后=${byKey}`); ok('明暗切换·t 键', byKey !== persisted, `t 后=${byKey}`);
await page.screenshot({ path: `${SHOTS}/01-home-dark.png`, fullPage: false }); await page.screenshot({ path: `${SHOTS}/01-home-dark.png`, fullPage: false });
await page.screenshot({ path: `${SHOTS}/01b-home-dark-confirm.png` });
await page.keyboard.press('t'); // 回亮色 await page.keyboard.press('t'); // 回亮色
// 2) 软导航后切换不失效(本次修复的回归项) // 2) 软导航后切换不失效(本次修复的回归项)
@@ -93,6 +94,12 @@ ok('g-chords 导航到图库', page.url().includes('/gallery'));
await page.goto(BASE + '/gallery/', { waitUntil: 'networkidle' }); await page.goto(BASE + '/gallery/', { waitUntil: 'networkidle' });
ok('图库·灯箱默认关闭', await page.locator('#lb[open]').count() === 0); ok('图库·灯箱默认关闭', await page.locator('#lb[open]').count() === 0);
ok('图库·网格照片可见', (await page.locator('.wall .shot img:visible').count()) >= 15, `${await page.locator('.wall .shot img:visible').count()} 张`); ok('图库·网格照片可见', (await page.locator('.wall .shot img:visible').count()) >= 15, `${await page.locator('.wall .shot img:visible').count()} 张`);
// fullPage 截图不必然触发视口外懒加载:先整页滚动一圈再截(视觉复核 19/21 教训)
await page.evaluate(async () => {
for (let y = 0; y <= document.body.scrollHeight; y += 400) { window.scrollTo(0, y); await new Promise(r => setTimeout(r, 60)); }
window.scrollTo(0, 0);
});
await page.waitForTimeout(600);
await page.screenshot({ path: `${SHOTS}/05-gallery-light.png`, fullPage: true }); await page.screenshot({ path: `${SHOTS}/05-gallery-light.png`, fullPage: true });
await page.click('.shot-btn >> nth=0'); await page.click('.shot-btn >> nth=0');
await page.waitForTimeout(300); await page.waitForTimeout(300);