feat(interact): 访问量小字+瞬间时间线动效(车道 F)
- pv:ArtalkComments/MomentsPage 组件级独立 POST /artalk/api/v2/ pages/pv(页面加载即计,不等懒加载;POST 天然绕 ESA 边缘缓存); init 加 pvAdd:false 防双计(默认 true 此前评论加载一直在默默计 数——mian-sha pv=2 即此来源);双轨 pageKey 分开计数 - 瞬间动效:.tl-item IntersectionObserver 进入视口一次性渐显 (opacity 0+8px);降级链=data-mom-anim 前置标记——无 JS/ reduced-motion/无 IO 三情形标记缺席则初始隐藏不生效;软导航 astro:page-load 重注册 - Artalk 后台真根因修复(车道侧宿主件,非仓内):容器日志实锤 登录从未成功过(12+ 次 401/400,非会话丢失)——CLI 重置密码+ 凭据入 INITIAL-CREDENTIALS.txt 0600+login_timeout 3d→30d+ 容器重启;API 全链 200 实测 - 遗留(ESA 侧归 orchestrator):ESA 缓存 /artalk/api GET 响应 (stats Age 头实锤),需边缘规则绕缓存 车道:op-artalk(自验 8/8 含 route mock/reduced-motion/无 JS 三降 级);收编验收:pv 元素+动效标记+reduced-motion 豁免实测 验证: pnpm astro build exit 0;E2E 33/33;三降级链 playwright 实测 Spec: none (pv+动效,车道级交付) Co-authored-by: GLM-5.3
This commit is contained in:
1 parent
d00ca7e447
commit
55cfc73cb7
3 files changed
+111
No files matched your search
@@ -6,8 +6,16 @@ interface Props {
|
||||
pageTitle: string;
|
||||
}
|
||||
const { pageKey, pageTitle } = Astro.props;
|
||||
import { t } from '../i18n/ui';
|
||||
import { localeOf } from '../lib/locale';
|
||||
const locale = localeOf(Astro.url.pathname);
|
||||
---
|
||||
|
||||
<!-- pv 小字:计数由本组件下方脚本独立 POST(页面加载即计),与 Artalk init 的 pvAdd 解耦 -->
|
||||
<p class="atk-pv-line" aria-label={t('pv_viewed', locale)}>
|
||||
<span data-artalk-pv data-page-key={pageKey} data-page-title={pageTitle}>–</span>
|
||||
{' '}{t('pv_viewed', locale)}
|
||||
</p>
|
||||
<div id="artalk-container" data-page-key={pageKey} data-page-title={pageTitle}></div>
|
||||
|
||||
<!-- Artalk 主题接管:--at-color-* 全套映射到站点 tokens(值均为 var(),明暗随
|
||||
@@ -68,9 +76,37 @@ const { pageKey, pageTitle } = Astro.props;
|
||||
#artalk-container .atk-content a:hover {
|
||||
text-decoration-color: var(--accent);
|
||||
}
|
||||
.atk-pv-line {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
color: var(--ink-4);
|
||||
margin: var(--space-2) 0 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// 访问量:POST /artalk/api/v2/pages/pv 计数并取数(POST 天然绕过 ESA 边缘缓存;
|
||||
// GET /stats/* 会被 ESA 缓存出冻结数字,勿改查询式)。page-load 兼容软导航换页重计。
|
||||
const bumpPv = async () => {
|
||||
const el = document.querySelector('[data-artalk-pv]');
|
||||
if (!el) return;
|
||||
const key = el.dataset.pageKey ?? '';
|
||||
if (!key) return;
|
||||
try {
|
||||
const res = await fetch('/artalk/api/v2/pages/pv', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ page_key: key, page_title: el.dataset.pageTitle ?? '', site_name: 'wait4x.online' }),
|
||||
});
|
||||
const d = await res.json();
|
||||
el.textContent = String(d.pv ?? '–');
|
||||
} catch {
|
||||
el.textContent = '–'; // 计数失败静默:小字不可见性优于报错
|
||||
}
|
||||
};
|
||||
document.addEventListener('astro:page-load', () => void bumpPv());
|
||||
void bumpPv();
|
||||
|
||||
const el = document.getElementById('artalk-container');
|
||||
if (el) {
|
||||
const mount = () => {
|
||||
@@ -103,6 +139,8 @@ const { pageKey, pageTitle } = Astro.props;
|
||||
locale: 'zh-CN',
|
||||
// 锁亮皮:明暗由页面 tokens 接管(见上方 --at-* 映射),避免双暗色机制叠加
|
||||
darkMode: false,
|
||||
// pv 计数移交组件级独立 POST(页面加载即计,不等评论区懒加载进入视口)
|
||||
pvAdd: false,
|
||||
});
|
||||
};
|
||||
// 进入视口 ~200px 前挂载;无 IO 环境直接挂
|
||||
|
||||
@@ -20,6 +20,10 @@ const iso = (d: Date) => d.toISOString();
|
||||
<BaseLayout title={`${t('moments_title', locale)} · wait4x`}>
|
||||
<h1 class="page-title">{t('moments_title', locale)}</h1>
|
||||
<p class="page-note">{t('moments_note', locale)}</p>
|
||||
<p class="pv-line" aria-label={t('pv_viewed', locale)}>
|
||||
<span data-artalk-pv data-page-key={locale === 'en' ? '/en/moments/' : '/moments/'}>–</span>
|
||||
{' '}{t('pv_viewed', locale)}
|
||||
</p>
|
||||
<ol class="tl">
|
||||
{moments.map((m) => (
|
||||
<li class="tl-item">
|
||||
@@ -54,6 +58,56 @@ const iso = (d: Date) => d.toISOString();
|
||||
</ol>
|
||||
</BaseLayout>
|
||||
|
||||
<script>
|
||||
// 访问量:与 ArtalkComments 同款独立 POST(双轨 pageKey 分开计数;POST 绕 ESA 缓存)
|
||||
const bumpPv = async () => {
|
||||
const el = document.querySelector('[data-artalk-pv]');
|
||||
if (!el) return;
|
||||
const key = el.dataset.pageKey ?? '';
|
||||
if (!key) return;
|
||||
try {
|
||||
const res = await fetch('/artalk/api/v2/pages/pv', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ page_key: key, site_name: 'wait4x.online' }),
|
||||
});
|
||||
const d = await res.json();
|
||||
el.textContent = String(d.pv ?? '–');
|
||||
} catch {
|
||||
el.textContent = '–';
|
||||
}
|
||||
};
|
||||
|
||||
// 时间线渐显:html[data-mom-anim] 由 JS 前置标记——无 JS / reduced-motion 时
|
||||
// 标记缺席,CSS 初始隐藏规则整体不生效(渐进降级,元素永远可见)
|
||||
let io: IntersectionObserver | null = null;
|
||||
const armAnim = () => {
|
||||
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
||||
if (!('IntersectionObserver' in window)) return;
|
||||
document.documentElement.dataset.momAnim = 'on';
|
||||
io?.disconnect();
|
||||
io = new IntersectionObserver(
|
||||
(entries) => {
|
||||
for (const e of entries) {
|
||||
if (e.isIntersecting) {
|
||||
e.target.classList.add('in-view');
|
||||
io?.unobserve(e.target);
|
||||
}
|
||||
}
|
||||
},
|
||||
{ rootMargin: '0px 0px -8% 0px' },
|
||||
);
|
||||
document.querySelectorAll('.tl-item:not(.in-view)').forEach((el) => io?.observe(el));
|
||||
};
|
||||
|
||||
document.addEventListener('astro:page-load', () => {
|
||||
void bumpPv();
|
||||
armAnim();
|
||||
});
|
||||
void bumpPv();
|
||||
armAnim();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.page-title {
|
||||
font-size: var(--text-h1);
|
||||
@@ -147,4 +201,20 @@ const iso = (d: Date) => d.toISOString();
|
||||
.mom-card { padding: var(--space-2) var(--space-3); }
|
||||
.mom-fig { max-width: 100%; }
|
||||
}
|
||||
.pv-line {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
color: var(--ink-4);
|
||||
margin: 0 0 var(--space-4);
|
||||
}
|
||||
/* 初始隐藏仅在 JS 标记存在时生效:宁轻勿闹——透明度+8px 上移,进入视口一次性点亮 */
|
||||
[data-mom-anim] .tl-item {
|
||||
opacity: 0;
|
||||
translate: 0 8px;
|
||||
transition: opacity 0.45s var(--ease), translate 0.45s var(--ease);
|
||||
}
|
||||
[data-mom-anim] .tl-item.in-view {
|
||||
opacity: 1;
|
||||
translate: 0 0;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user