@@ -42,19 +42,28 @@ const AUTO_PLAY_INTERVAL = 10000
4242let autoPlayTimer: number | undefined
4343// 用于驱动自动翻页进度条动画重启
4444const progressSeed = ref (0 )
45+ /** 是否开启自动播放(默认开启) */
46+ const autoPlayEnabled = ref (true )
4547
4648const resetAutoPlay = () => {
4749 if (typeof window === ' undefined' ) return
4850 if (autoPlayTimer !== undefined ) {
4951 window .clearInterval (autoPlayTimer )
52+ autoPlayTimer = undefined
53+ }
54+ if (autoPlayEnabled .value ) {
55+ autoPlayTimer = window .setInterval (() => {
56+ goToNextPage ()
57+ }, AUTO_PLAY_INTERVAL )
5058 }
51- autoPlayTimer = window .setInterval (() => {
52- goToNextPage ()
53- }, AUTO_PLAY_INTERVAL )
54- // 重启进度条动画
5559 progressSeed .value ++
5660}
5761
62+ const toggleAutoPlay = () => {
63+ autoPlayEnabled .value = ! autoPlayEnabled .value
64+ resetAutoPlay ()
65+ }
66+
5867onMounted (() => {
5968 calcCols ()
6069 if (typeof window !== ' undefined' ) {
@@ -120,7 +129,9 @@ const goToNextPage = () => {
120129type DisplayItem = {
121130 id: string
122131 mainSrc: string
132+ mainSrcPng: string
123133 mainThumbSrc: string
134+ mainThumbSrcPng: string
124135 overlaySrc: string
125136 overlayThumbSrc: string
126137 skeletonSrc: string
@@ -161,13 +172,18 @@ const displayItems = computed<DisplayItem[]>(() => {
161172 const id = ids [indexInPage ]
162173 if (id ) {
163174 const mainSrc = ` ${baseUrl }dataset/${id }/images/look/${id }.jpg `
175+ const mainSrcPng = ` ${baseUrl }dataset/${id }/images/look/${id }.png `
176+ const mainThumbSrc = ` ${baseUrl }thumbnail/${id }/images/look/${id }.jpg `
177+ const mainThumbSrcPng = ` ${baseUrl }thumbnail/${id }/images/look/${id }.png `
164178 const overlaySrc = ` ${baseUrl }dataset/${id }/images/segment/color_segmentation.png `
165179 const skeletonSrc = ` ${baseUrl }dataset/${id }/images/dwpose/${id }.png `
166180
167181 items .push ({
168182 id ,
169183 mainSrc ,
170- mainThumbSrc: mainSrc .replace (` ${baseUrl }dataset/ ` , ` ${baseUrl }thumbnail/ ` ),
184+ mainSrcPng ,
185+ mainThumbSrc ,
186+ mainThumbSrcPng ,
171187 overlaySrc ,
172188 overlayThumbSrc: overlaySrc .replace (` ${baseUrl }dataset/ ` , ` ${baseUrl }thumbnail/ ` ),
173189 skeletonSrc ,
@@ -178,39 +194,42 @@ const displayItems = computed<DisplayItem[]>(() => {
178194 }
179195
180196 // 没有显式指定 id 的格子统一使用 loading 占位图
197+ const loading = ` ${baseUrl }dataset/loading.jpg `
181198 items .push ({
182199 id: ` placeholder-${currentPage .value }-${i } ` ,
183- mainSrc: ` ${baseUrl }dataset/loading.jpg ` ,
184- mainThumbSrc: ` ${baseUrl }dataset/loading.jpg ` ,
185- overlaySrc: ` ${baseUrl }dataset/loading.jpg ` ,
186- overlayThumbSrc: ` ${baseUrl }dataset/loading.jpg ` ,
187- skeletonSrc: ` ${baseUrl }dataset/loading.jpg ` ,
188- skeletonThumbSrc: ` ${baseUrl }dataset/loading.jpg ` ,
200+ mainSrc: loading ,
201+ mainSrcPng: loading ,
202+ mainThumbSrc: loading ,
203+ mainThumbSrcPng: loading ,
204+ overlaySrc: loading ,
205+ overlayThumbSrc: loading ,
206+ skeletonSrc: loading ,
207+ skeletonThumbSrc: loading ,
189208 })
190209 }
191210
192211 return items
193212})
194213
195- // 矩阵图优先用缩略图(省流),404 再回退原图; look 还有 jpg→ png 回退
214+ // look 图回退顺序:thumbnail jpg → thumbnail png → dataset jpg → dataset png
196215const lookUrlCache = ref <Record <string , string >>({})
197216const overlayUrlCache = ref <Record <string , string >>({})
198217const skeletonUrlCache = ref <Record <string , string >>({})
199218
219+ const LOOK_FALLBACK_ORDER = (item : DisplayItem ) =>
220+ [item .mainThumbSrc , item .mainThumbSrcPng , item .mainSrc , item .mainSrcPng ] as const
221+
200222function getMainLookSrc(item : DisplayItem ): string {
201- return lookUrlCache .value [item .id ] ?? item .mainThumbSrc ?? item . mainSrc
223+ return lookUrlCache .value [item .id ] ?? item .mainThumbSrc
202224}
203225
204226function onLookImageError(item : DisplayItem ) {
205227 if (item .id .startsWith (' placeholder-' )) return
206- const tried = lookUrlCache .value [item .id ] ?? item .mainThumbSrc ?? item .mainSrc
207- if (tried === item .mainThumbSrc ) {
208- lookUrlCache .value = { ... lookUrlCache .value , [item .id ]: item .mainSrc }
209- } else if (tried .endsWith (' .jpg' )) {
210- lookUrlCache .value = {
211- ... lookUrlCache .value ,
212- [item .id ]: ` ${baseUrl }dataset/${item .id }/images/look/${item .id }.png ` ,
213- }
228+ const order = LOOK_FALLBACK_ORDER (item )
229+ const tried = lookUrlCache .value [item .id ] ?? item .mainThumbSrc
230+ const idx = order .indexOf (tried )
231+ if (idx >= 0 && idx < order .length - 1 ) {
232+ lookUrlCache .value = { ... lookUrlCache .value , [item .id ]: order [idx + 1 ] }
214233 }
215234}
216235
@@ -364,7 +383,7 @@ const closeDetail = () => {
364383 :key =" progressSeed"
365384 :style =" {
366385 animationDuration: AUTO_PLAY_INTERVAL + 'ms',
367- animationPlayState: isDetailVisible ? 'paused' : 'running',
386+ animationPlayState: isDetailVisible || !autoPlayEnabled ? 'paused' : 'running',
368387 }"
369388 ></div >
370389 </div >
@@ -389,6 +408,13 @@ const closeDetail = () => {
389408 >
390409 ›
391410 </button >
411+ <span class =" auto-play-wrap" >
412+ <span class =" auto-play-label" >Auto-play</span >
413+ <label class =" auto-play-switch" :title =" autoPlayEnabled ? 'Disable auto-play' : 'Enable auto-play'" >
414+ <input type =" checkbox" :checked =" autoPlayEnabled" @change =" toggleAutoPlay" />
415+ <span class =" auto-play-slider" ></span >
416+ </label >
417+ </span >
392418 </div >
393419
394420 <OutfitDetailModal
@@ -564,6 +590,63 @@ const closeDetail = () => {
564590 transform : none ;
565591}
566592
593+ /* 自动播放:文字 + 开关 */
594+ .auto-play-wrap {
595+ display : inline-flex ;
596+ align-items : center ;
597+ gap : 8px ;
598+ font-size : 12px ;
599+ color : #666666 ;
600+ }
601+
602+ .auto-play-label {
603+ user-select : none ;
604+ }
605+
606+ .auto-play-switch {
607+ position : relative ;
608+ display : inline-block ;
609+ width : 40px ;
610+ height : 22px ;
611+ flex-shrink : 0 ;
612+ cursor : pointer ;
613+ }
614+
615+ .auto-play-switch input {
616+ opacity : 0 ;
617+ width : 0 ;
618+ height : 0 ;
619+ }
620+
621+ .auto-play-slider {
622+ position : absolute ;
623+ inset : 0 ;
624+ border-radius : 22px ;
625+ background : #d0d0d0 ;
626+ transition : background 0.25s ease ;
627+ }
628+
629+ .auto-play-slider ::before {
630+ content : ' ' ;
631+ position : absolute ;
632+ width : 18px ;
633+ height : 18px ;
634+ left : 2px ;
635+ top : 2px ;
636+ border-radius : 50% ;
637+ background : #ffffff ;
638+ box-shadow : 0 1px 3px rgba (0 , 0 , 0 , 0.2 );
639+ transition : transform 0.25s ease ;
640+ }
641+
642+ .auto-play-switch input :checked + .auto-play-slider {
643+ background : #409eff ;
644+ }
645+
646+ .auto-play-switch input :checked + .auto-play-slider ::before {
647+ transform : translateX (18px );
648+ }
649+
567650.nav-btn :hover {
568651 background : #e0e0e0 ;
569652}
0 commit comments