From 38b913b429841940878ed91b30aca0d7e8876feb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Tue, 29 Sep 2026 14:45:40 +0800 Subject: [PATCH] =?UTF-8?q?style(viewer):=20=E5=B9=BF=E5=91=8A=E8=BD=AE?= =?UTF-8?q?=E6=92=AD=E6=97=A0=E7=BC=9D=E7=8E=AF=E5=9B=9E=E2=80=94=E2=80=94?= =?UTF-8?q?=E9=A6=96=E5=B0=BE=E5=85=8B=E9=9A=86=E5=B8=A7=20+=20=E5=88=B0?= =?UTF-8?q?=E4=BD=8D=E7=9E=AC=E6=97=B6=E5=9B=9E=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 末帧→首帧不再反向长滚:经尾克隆单帧宽右→左自然滑入,过渡结束后 关过渡瞬时跳回等视觉真实帧;首帧→末帧(右滑/点末点)对称走头克隆。 Co-Authored-By: Claude Code --- apps/viewer/src/__tests__/ad-carousel.spec.ts | 20 +++- apps/viewer/src/components/ads/AdCarousel.vue | 111 +++++++++++++++--- 2 files changed, 112 insertions(+), 19 deletions(-) diff --git a/apps/viewer/src/__tests__/ad-carousel.spec.ts b/apps/viewer/src/__tests__/ad-carousel.spec.ts index 24596f5..88f6919 100644 --- a/apps/viewer/src/__tests__/ad-carousel.spec.ts +++ b/apps/viewer/src/__tests__/ad-carousel.spec.ts @@ -38,7 +38,8 @@ function currentSrc(wrapper: VueWrapper): string | null { /** 图加载失败(含现签 URL 过期)→ emit imageError(adId)(2026-09-29 裂图自愈)。 */ function triggerImgError(wrapper: VueWrapper): void { - wrapper.find('.ad-slide img').trigger('error'); + // 触发活跃帧的图错误(头/尾克隆帧常驻轨道,首个 .ad-slide 是头克隆)。 + wrapper.find('.ad-slide.active img').trigger('error'); } describe('AdCarousel', () => { @@ -81,7 +82,7 @@ describe('AdCarousel', () => { it('opens the current ad link in a new tab via window.open', async () => { const wrapper = mountCarousel(); - await wrapper.get('.ad-slide').trigger('click'); + await wrapper.get('.ad-slide.active').trigger('click'); expect(openSpy).toHaveBeenCalledWith('https://link.test/a', '_blank', 'noopener,noreferrer'); }); @@ -128,4 +129,19 @@ describe('AdCarousel', () => { expect(emitted).toHaveLength(1); expect(emitted![0]).toEqual(['a']); // 当前帧(index 0)的 adId }); + + it('wraps last→first as one natural leftward slide then a silent snap', async () => { + const wrapper = mountCarousel(ADS); + await vi.advanceTimersByTimeAsync(8000); // a→b→c(真实末帧) + expect(currentSrc(wrapper)).toBe('https://ad.test/c.jpg'); + expect(wrapper.find('.ad-slides').attributes('style')).toContain('translateX(-300%)'); + await vi.advanceTimersByTimeAsync(4000); // 环回:a 经尾克隆右→左滑入 + expect(currentSrc(wrapper)).toBe('https://ad.test/a.jpg'); // 活跃真实帧已回 a + let track = wrapper.find('.ad-slides'); + expect(track.attributes('style')).toContain('translateX(-400%)'); // 视觉在尾克隆位 + expect(track.classes()).not.toContain('no-transition'); + await vi.advanceTimersByTimeAsync(600); // 过渡结束 → 关过渡瞬时回位 + track = wrapper.find('.ad-slides'); + expect(track.attributes('style')).toContain('translateX(-100%)'); // 等视觉真实首帧 + }); }); diff --git a/apps/viewer/src/components/ads/AdCarousel.vue b/apps/viewer/src/components/ads/AdCarousel.vue index c6c7826..d1cb670 100644 --- a/apps/viewer/src/components/ads/AdCarousel.vue +++ b/apps/viewer/src/components/ads/AdCarousel.vue @@ -1,5 +1,5 @@