feat(viewer): likes, bubbles, visit reporting and hot sorting
Task 10 viewer 互动化: - 点赞:useLikes 批次状态机(500ms 防抖 / 5 次满额即冲 / 失败同 batchId 退避重试 3 次后回滚丢弃),乐观计数 + 服务端 total 权威校准 (countVersion 单调),大图与格子双入口; - WS 实时:RealtimeClient 纯状态机(半频心跳、指数退避重连、三败降级、 重连成功 resync 第 1 页重取),消息路由器做 likes.burst 自身过滤 (originBatchIds 求交)、隐藏页暂停与减少动画下仅校准不出气泡; photo.hidden/deleted 即时移出序列并回退邻居大图;降级后 8s 轮询接管; - 浏览上报:visitorId 跨标签 BroadcastChannel first-writer-wins 协调, 可见时上报、requestId 持久幂等、网络失败指数重试; - 排序:最新/热门 chips(热门走 ov 冷快照),大图打开期间禁用切换; 顶栏「减少动画」偏好开关; - 契约包新增 ./realtime 子路径出口(浏览器面不可引桶出口——其连带 internal-sync 的 node:crypto 会被 Vite 外置化致整页崩溃); - e2e 注入钩子 __viewerWsUrl/__viewerWsRetryBaseMs/__viewerRealtimePollMs/ __viewerBubbleMs 与 .activity[data-connection] 连接态锚。 vitest:viewer 105(新增 36:likes 批次 8、visitor 协调 5、上报门控 4、 路由 8、WS 状态机 7、排序 4);Playwright:viewer 21(新增 likes.spec 7、 journey 补互动腿断言)。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
getPublicPhotos,
|
||||
type PublicPhoto,
|
||||
type PublicPhotoList,
|
||||
type SortMode,
|
||||
} from '../api/client';
|
||||
import { errorMessageKey } from '../api/errors';
|
||||
import { containsNewPhotoId, mergeUniqueByPhotoId } from './layout';
|
||||
@@ -29,6 +30,8 @@ export interface PhotoSequenceInputs {
|
||||
token: Ref<string>;
|
||||
locale: Ref<string>;
|
||||
category: Ref<string | null>;
|
||||
/** 排序模式(Task 10 热门/最新):切换=快照重取(整表重置,游标重走)。 */
|
||||
sort?: Ref<SortMode>;
|
||||
}
|
||||
|
||||
/** 单图刷新结果:换绑新签名 URL / 照片已不可公开见(404,已从序列移除)/ 其他失败。 */
|
||||
@@ -55,6 +58,7 @@ export function usePhotoSequence(inputs: PhotoSequenceInputs) {
|
||||
return getPublicPhotos(inputs.token.value, {
|
||||
locale: inputs.locale.value,
|
||||
category: inputs.category.value ?? undefined,
|
||||
sort: inputs.sort?.value,
|
||||
cursor,
|
||||
});
|
||||
}
|
||||
@@ -236,8 +240,8 @@ export function usePhotoSequence(inputs: PhotoSequenceInputs) {
|
||||
document.removeEventListener('visibilitychange', onVisibilityChange);
|
||||
});
|
||||
|
||||
// token/语言/分类变化:整表重置(语言切换保留当前分类——inputs 自带)。
|
||||
watch([inputs.token, inputs.locale, inputs.category], () => void resetAndLoad());
|
||||
// token/语言/分类/排序变化:整表重置(语言切换保留当前分类——inputs 自带)。
|
||||
watch([inputs.token, inputs.locale, inputs.category, () => inputs.sort?.value], () => void resetAndLoad());
|
||||
|
||||
// reactive 包装:模板/脚本访问 sequence.photos 等 ref 自动解包(普通对象不会)。
|
||||
return reactive({
|
||||
|
||||
Reference in New Issue
Block a user