|
| 1 | +<script setup lang="ts"> |
| 2 | +import { useBewlyApp } from '~/composables/useAppProvider' |
| 3 | +import type { GridLayoutType } from '~/logic' |
| 4 | +import { settings } from '~/logic' |
| 5 | +import type { PopularSeriesItem, PopularSeriesListResult, PopularSeriesOneResult, PopularSeriesVideoItem } from '~/models/video/popularSeries' |
| 6 | +import api from '~/utils/api' |
| 7 | +
|
| 8 | +const props = defineProps<{ |
| 9 | + gridLayout: GridLayoutType |
| 10 | + topBarVisibility: boolean |
| 11 | +}>() |
| 12 | +
|
| 13 | +const emit = defineEmits<{ |
| 14 | + (e: 'beforeLoading'): void |
| 15 | + (e: 'afterLoading'): void |
| 16 | +}>() |
| 17 | +
|
| 18 | +const { handleBackToTop, handlePageRefresh } = useBewlyApp() |
| 19 | +
|
| 20 | +const gridClass = computed((): string => { |
| 21 | + if (props.gridLayout === 'adaptive') |
| 22 | + return 'grid-adaptive' |
| 23 | + if (props.gridLayout === 'twoColumns') |
| 24 | + return 'grid-two-columns' |
| 25 | + return 'grid-one-column' |
| 26 | +}) |
| 27 | +
|
| 28 | +const gridStyle = computed(() => { |
| 29 | + if (props.gridLayout !== 'adaptive') |
| 30 | + return {} |
| 31 | + const style: Record<string, any> = { |
| 32 | + display: 'grid', |
| 33 | + gridTemplateColumns: 'repeat(auto-fill, minmax(var(--bew-home-card-min-width, 280px), 1fr))', |
| 34 | + } |
| 35 | + const baseWidth = Math.max(120, settings.value.homeAdaptiveCardMinWidth || 280) |
| 36 | + style['--bew-home-card-min-width'] = `${baseWidth}px` |
| 37 | + return style |
| 38 | +}) |
| 39 | +
|
| 40 | +const isLoading = ref<boolean>(false) |
| 41 | +const shouldMoveAsideUp = ref<boolean>(false) |
| 42 | +
|
| 43 | +const seriesList = ref<PopularSeriesItem[]>([]) |
| 44 | +const activatedSeries = ref<PopularSeriesItem | null>(null) |
| 45 | +const videoList = ref<PopularSeriesVideoItem[]>([]) |
| 46 | +
|
| 47 | +watch(() => props.topBarVisibility, () => { |
| 48 | + shouldMoveAsideUp.value = false |
| 49 | +
|
| 50 | + if (settings.value.autoHideTopBar && settings.value.showTopBar) { |
| 51 | + if (props.topBarVisibility) |
| 52 | + shouldMoveAsideUp.value = false |
| 53 | + else |
| 54 | + shouldMoveAsideUp.value = true |
| 55 | + } |
| 56 | +}) |
| 57 | +
|
| 58 | +onMounted(() => { |
| 59 | + initData() |
| 60 | + initPageAction() |
| 61 | +}) |
| 62 | +
|
| 63 | +onActivated(() => { |
| 64 | + initPageAction() |
| 65 | +}) |
| 66 | +
|
| 67 | +function initPageAction() { |
| 68 | + handlePageRefresh.value = async () => { |
| 69 | + if (isLoading.value) |
| 70 | + return |
| 71 | + initData() |
| 72 | + } |
| 73 | +} |
| 74 | +
|
| 75 | +function initData() { |
| 76 | + videoList.value.length = 0 |
| 77 | + seriesList.value.length = 0 |
| 78 | + activatedSeries.value = null |
| 79 | + getSeriesList() |
| 80 | +} |
| 81 | +
|
| 82 | +function getSeriesList() { |
| 83 | + api.ranking.getPopularSeriesList() |
| 84 | + .then((res: PopularSeriesListResult) => { |
| 85 | + if (res && res.code === 0 && res.data && Array.isArray(res.data.list)) { |
| 86 | + // sort by number desc (latest first) if available |
| 87 | + seriesList.value = [...res.data.list].sort((a, b) => (b.number || 0) - (a.number || 0)) |
| 88 | + if (seriesList.value.length) { |
| 89 | + // 默认选择第一期(通常为最新期) |
| 90 | + activatedSeries.value = seriesList.value[0] |
| 91 | + handleBackToTop(settings.value.useSearchPageModeOnHomePage ? 510 : 0) |
| 92 | + getSeriesOne() |
| 93 | + } |
| 94 | + } |
| 95 | + }) |
| 96 | +} |
| 97 | +
|
| 98 | +function getSeriesOne() { |
| 99 | + if (!activatedSeries.value) |
| 100 | + return |
| 101 | + emit('beforeLoading') |
| 102 | + isLoading.value = true |
| 103 | + videoList.value.length = 0 |
| 104 | + api.ranking.getPopularSeriesOne({ |
| 105 | + number: (activatedSeries.value as PopularSeriesItem).number, |
| 106 | + }).then((res: PopularSeriesOneResult) => { |
| 107 | + if (res && res.code === 0 && res.data && Array.isArray(res.data.list)) |
| 108 | + videoList.value = res.data.list |
| 109 | + }).finally(() => { |
| 110 | + isLoading.value = false |
| 111 | + emit('afterLoading') |
| 112 | + }) |
| 113 | +} |
| 114 | +
|
| 115 | +watch(() => activatedSeries.value?.number, (newVal, oldVal) => { |
| 116 | + if (newVal && newVal !== oldVal) { |
| 117 | + handleBackToTop(settings.value.useSearchPageModeOnHomePage ? 510 : 0) |
| 118 | + getSeriesOne() |
| 119 | + } |
| 120 | +}) |
| 121 | +
|
| 122 | +defineExpose({ initData }) |
| 123 | +</script> |
| 124 | + |
| 125 | +<template> |
| 126 | + <div flex="~ gap-40px"> |
| 127 | + <aside |
| 128 | + pos="sticky top-150px" h="[calc(100vh-140px)]" w-200px shrink-0 duration-300 |
| 129 | + ease-in-out |
| 130 | + :class="{ hide: shouldMoveAsideUp }" |
| 131 | + > |
| 132 | + <OverlayScrollbarsComponent h-inherit p-20px m--20px defer> |
| 133 | + <ul flex="~ col gap-2"> |
| 134 | + <li v-for="item in seriesList" :key="item.number"> |
| 135 | + <a |
| 136 | + :class="{ active: activatedSeries?.number === item.number }" |
| 137 | + px-4 lh-30px h-30px hover:bg="$bew-fill-2" w-inherit |
| 138 | + block rounded="$bew-radius" cursor-pointer transition="all 300 ease-out" |
| 139 | + hover:scale-105 un-text="$bew-text-1" |
| 140 | + @click="activatedSeries = item" |
| 141 | + > |
| 142 | + <span class="issue-label">{{ item.name || `第${item.number}期` }}</span> |
| 143 | + </a> |
| 144 | + </li> |
| 145 | + </ul> |
| 146 | + </OverlayScrollbarsComponent> |
| 147 | + </aside> |
| 148 | + |
| 149 | + <main w-full :class="gridClass" :style="gridStyle"> |
| 150 | + <VideoCard |
| 151 | + v-for="(video, index) in videoList" |
| 152 | + :key="`${video.aid}-${index}`" |
| 153 | + :video="{ |
| 154 | + id: Number(video.aid), |
| 155 | + duration: video.duration, |
| 156 | + title: video.title, |
| 157 | + desc: video.desc, |
| 158 | + cover: video.pic, |
| 159 | + author: { |
| 160 | + name: video.owner?.name, |
| 161 | + authorFace: video.owner?.face, |
| 162 | + mid: video.owner?.mid, |
| 163 | + }, |
| 164 | + view: video.stat?.view, |
| 165 | + danmaku: video.stat?.danmaku, |
| 166 | + publishedTimestamp: video.pubdate, |
| 167 | + bvid: video.bvid, |
| 168 | + cid: video.cid, |
| 169 | + }" |
| 170 | + show-preview |
| 171 | + :horizontal="gridLayout !== 'adaptive'" |
| 172 | + w-full |
| 173 | + /> |
| 174 | + |
| 175 | + <!-- skeleton --> |
| 176 | + <template v-if="isLoading"> |
| 177 | + <VideoCardSkeleton |
| 178 | + v-for="item in 30" :key="item" |
| 179 | + :horizontal="gridLayout !== 'adaptive'" |
| 180 | + /> |
| 181 | + </template> |
| 182 | + </main> |
| 183 | + </div> |
| 184 | +</template> |
| 185 | + |
| 186 | +<style lang="scss" scoped> |
| 187 | +.active { |
| 188 | + --uno: "scale-110 bg-$bew-theme-color-auto text-$bew-text-auto shadow-$bew-shadow-2"; |
| 189 | +} |
| 190 | +
|
| 191 | +.hide { |
| 192 | + --uno: "h-[calc(100vh-70)] translate-y--70px"; |
| 193 | +} |
| 194 | +
|
| 195 | +.grid-adaptive { |
| 196 | + --uno: "grid gap-5"; |
| 197 | + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); |
| 198 | +} |
| 199 | +
|
| 200 | +.grid-two-columns { |
| 201 | + --uno: "grid cols-1 xl:cols-2 gap-4"; |
| 202 | +} |
| 203 | +
|
| 204 | +.grid-one-column { |
| 205 | + --uno: "grid cols-1 gap-4"; |
| 206 | +} |
| 207 | +.issue-label { |
| 208 | + --uno: "text-13px truncate"; |
| 209 | +} |
| 210 | +</style> |
0 commit comments