feat(viewer): UI-02 dark immersive photo viewer with thumbnail strip
V04 (方案 §5):PhotoLightbox 模板重构为深色沉浸布局——顶栏(关闭/位置 计数/⋯ 减少动画菜单)+ 舞台(contain 大图 + 边缘 ‹› + 粒子/互动占位层)+ 缩略图条 + 底部操作栏;业务逻辑(序列导航/缩放手势/签名刷新/下载/点赞) 原样保留。 - 新增 viewer/position.ts:§5.2 位置计数真实回退(solo→照片预览;无 total 未到末页→已加载 N 张;全部加载→i/n;不伪造总数) - 新增 ThumbnailStrip:当前 ±3 窗口(≤7 项)、主色 2px 描边、thumb 变体、 滚入可见(遵守减少动画)、点击选择不新增历史 - 新增 PhotoActionBar:Like(coral 心形+计数+连续点赞提示)/Share/ Download 均分大触控;下载关闭整项移除;原图二级菜单(▾ + 长按) - 模态可用性:body 滚动锁、焦点进入/困住/归还触发元素、Esc 先收菜单、 菜单内左右键不抢占、dvh + 安全区、chrome 区手势/滚轮隔离 - 分享 V04 最小实现(复制当前深链,support/clipboard 从 AlbumHeader 提取共用);navigator.share 编排属 Task 5 - zh-CN/en 新词条;vitest 新增 23 例(位置回退/缩略图/操作栏/焦点/滚锁) Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -187,16 +187,16 @@ describe('photo download (P4a Task 4)', () => {
|
||||
// on/off:单按钮,无 caret/菜单。
|
||||
const browseOnly = mountLightbox(makeAlbum());
|
||||
await flushPromises();
|
||||
expect(browseOnly.find('.control.download').exists()).toBe(true);
|
||||
expect(browseOnly.find('.control.download-caret').exists()).toBe(false);
|
||||
expect(browseOnly.find('.download-main').exists()).toBe(true);
|
||||
expect(browseOnly.find('.download-caret').exists()).toBe(false);
|
||||
|
||||
// on/on:分体按钮(caret 出现,菜单默认收起)。
|
||||
const split = mountLightbox(makeAlbum({ originalDownloadEnabled: true }));
|
||||
await flushPromises();
|
||||
expect(split.find('.control.download-caret').exists()).toBe(true);
|
||||
expect(split.find('.download-caret').exists()).toBe(true);
|
||||
expect(split.find('.download-menu').exists()).toBe(false);
|
||||
// 主按钮 aria 展开 = false。
|
||||
expect(split.find('.control.download-caret').attributes('aria-expanded')).toBe('false');
|
||||
expect(split.find('.download-caret').attributes('aria-expanded')).toBe('false');
|
||||
});
|
||||
|
||||
it('split menu offers both variants; picking original requests the original endpoint', async () => {
|
||||
@@ -204,9 +204,9 @@ describe('photo download (P4a Task 4)', () => {
|
||||
const wrapper = mountLightbox(makeAlbum({ originalDownloadEnabled: true }));
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('.control.download-caret').trigger('click');
|
||||
await wrapper.find('.download-caret').trigger('click');
|
||||
const items = wrapper.findAll('.download-menu button');
|
||||
expect(items.map((item) => item.text())).toEqual(['下载图片', '下载原图']);
|
||||
expect(items.map((item) => item.text())).toEqual(['浏览图', '原图']);
|
||||
|
||||
await items[1]!.trigger('click');
|
||||
await flushPromises();
|
||||
@@ -224,7 +224,7 @@ describe('photo download (P4a Task 4)', () => {
|
||||
const wrapper = mountLightbox(makeAlbum());
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('.control.download').trigger('click');
|
||||
await wrapper.find('.download-main').trigger('click');
|
||||
await flushPromises();
|
||||
expect(mockedGetPhotoDownload).toHaveBeenCalledTimes(2);
|
||||
expect(mockedGetPhotoDownload).toHaveBeenCalledWith('tok-dl', 'p1', 'browse');
|
||||
@@ -236,7 +236,7 @@ describe('photo download (P4a Task 4)', () => {
|
||||
const wrapper = mountLightbox(makeAlbum());
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('.control.download').trigger('click');
|
||||
await wrapper.find('.download-main').trigger('click');
|
||||
await flushPromises();
|
||||
expect(mockedGetPhotoDownload).toHaveBeenCalledTimes(1);
|
||||
expect(openSpy).toHaveBeenCalledWith('http://img.test/p1-browse.jpg', '_blank', 'noopener,noreferrer');
|
||||
@@ -245,7 +245,7 @@ describe('photo download (P4a Task 4)', () => {
|
||||
// 端点失败(网络/403 等)→ 失败提示可再见(再次点击重试)。
|
||||
openSpy.mockClear();
|
||||
mockedGetPhotoDownload.mockReset().mockRejectedValue(new Error('boom'));
|
||||
await wrapper.find('.control.download').trigger('click');
|
||||
await wrapper.find('.download-main').trigger('click');
|
||||
await flushPromises();
|
||||
expect(wrapper.find('.download-failed[role="alert"]').text()).toContain('下载失败');
|
||||
expect(openSpy).not.toHaveBeenCalled();
|
||||
@@ -259,7 +259,7 @@ describe('photo download (P4a Task 4)', () => {
|
||||
const wrapper = mountLightbox(makeAlbum());
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('.control.download').trigger('click');
|
||||
await wrapper.find('.download-main').trigger('click');
|
||||
await flushPromises();
|
||||
expect(wrapper.find('.download-failed[role="alert"]').text()).toContain('该相册未开放下载');
|
||||
expect(wrapper.find('.download-failed').text()).not.toContain('下载失败');
|
||||
@@ -270,7 +270,7 @@ describe('photo download (P4a Task 4)', () => {
|
||||
const wrapper = mountLightbox(makeAlbum());
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('.control.download').trigger('click');
|
||||
await wrapper.find('.download-main').trigger('click');
|
||||
await flushPromises();
|
||||
expect(wrapper.find('.wechat-guide[role="dialog"]').exists()).toBe(true);
|
||||
expect(wrapper.find('.wechat-guide h2').text()).toBe('保存图片指引');
|
||||
@@ -289,7 +289,7 @@ describe('photo download (P4a Task 4)', () => {
|
||||
const wrapper = mountLightbox(makeAlbum());
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('.control.download').trigger('click');
|
||||
await wrapper.find('.download-main').trigger('click');
|
||||
await wrapper.find('.guide-copy').trigger('click');
|
||||
await flushPromises();
|
||||
expect(writeText).toHaveBeenCalledWith(window.location.href);
|
||||
|
||||
Reference in New Issue
Block a user