mirror of
https://github.com/BewlyBewly/BewlyBewly.git
synced 2025-04-14 13:15:29 +00:00
* perf: video card * refactor: video card * refactor(ForYou): remove loading icon * refactor(ForYou): remove unused 'url' property in video card component * refactor(ForYou): remove unused 'url' property in video card component * fix(VideoCard): prevent `a` tag bubble * perf: improve video card performance * refactor(ForYou): optimize video card loading and rending * refactor(VideoCard): optimize video card loading and rendering * perf(ForYou): use another method to generate a unique id * perf(VideoCard): remove hover animation * feat(VideoCard): add hover & active effect again... * feat(VideoCard): adjust hovering & activating effect * perf(VideoCard): remove the hover effect * refactor(VideoCard): optimize lazy loading for images * perf: optimize scrolling performance * perf(VideoCard): optimize video preview performance
This commit is contained in:
@@ -3,14 +3,18 @@ import type { Ref } from 'vue'
|
||||
|
||||
import Button from '~/components/Button.vue'
|
||||
import Empty from '~/components/Empty.vue'
|
||||
import Loading from '~/components/Loading.vue'
|
||||
import VideoCard from '~/components/VideoCard/VideoCard.vue'
|
||||
import VideoCardSkeleton from '~/components/VideoCard/VideoCardSkeleton.vue'
|
||||
import { useApiClient } from '~/composables/api'
|
||||
import { useBewlyApp } from '~/composables/useAppProvider'
|
||||
import type { GridLayout } from '~/logic'
|
||||
import type { DataItem as MomentItem, MomentResult } from '~/models/moment/moment'
|
||||
|
||||
// https://github.com/starknt/BewlyBewly/blob/fad999c2e482095dc3840bb291af53d15ff44130/src/contentScripts/views/Home/components/ForYou.vue#L16
|
||||
interface VideoElement {
|
||||
uniqueId: string
|
||||
item?: MomentItem
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
gridLayout: GridLayout
|
||||
}>()
|
||||
@@ -30,7 +34,7 @@ const gridValue = computed((): string => {
|
||||
|
||||
const api = useApiClient()
|
||||
|
||||
const videoList = reactive<MomentItem[]>([])
|
||||
const videoList = ref<VideoElement[]>([])
|
||||
const isLoading = ref<boolean>(false)
|
||||
const needToLoginFirst = ref<boolean>(false)
|
||||
const containerRef = ref<HTMLElement>() as Ref<HTMLElement>
|
||||
@@ -68,7 +72,7 @@ function initPageAction() {
|
||||
async function initData() {
|
||||
offset.value = ''
|
||||
updateBaseline.value = ''
|
||||
videoList.length = 0
|
||||
videoList.value.length = 0
|
||||
noMoreContent.value = false
|
||||
|
||||
await getData()
|
||||
@@ -91,6 +95,16 @@ async function getFollowedUsersVideos() {
|
||||
emit('beforeLoading')
|
||||
isLoading.value = true
|
||||
try {
|
||||
let i = 0
|
||||
// https://github.com/starknt/BewlyBewly/blob/fad999c2e482095dc3840bb291af53d15ff44130/src/contentScripts/views/Home/components/ForYou.vue#L208
|
||||
// When video list is not empty, addthe number of pending videos is half of the page size
|
||||
// is set to prevent user scrolling the page too fast and causing the page too laggy
|
||||
const pendingVideos: VideoElement[] = Array.from({ length: videoList.value.length ? 10 : 30 }, () => ({
|
||||
uniqueId: `unique-id-${(videoList.value.length || 0) + i++})}`,
|
||||
} satisfies VideoElement))
|
||||
let lastVideoListLength = videoList.value.length
|
||||
videoList.value.push(...pendingVideos)
|
||||
|
||||
const response: MomentResult = await api.moment.getMoments({
|
||||
type: 'video',
|
||||
offset: Number(offset.value),
|
||||
@@ -114,12 +128,16 @@ async function getFollowedUsersVideos() {
|
||||
})
|
||||
|
||||
// when videoList has length property, it means it is the first time to load
|
||||
if (!videoList.length) {
|
||||
Object.assign(videoList, resData)
|
||||
if (!videoList.value.length) {
|
||||
videoList.value = resData.map(item => ({ uniqueId: `${item.id_str}`, item }))
|
||||
}
|
||||
else {
|
||||
// else we concat the new data to the old data
|
||||
Object.assign(videoList, videoList.concat(resData))
|
||||
resData.forEach((item) => {
|
||||
videoList.value[lastVideoListLength++] = {
|
||||
uniqueId: `${item.id_str}`,
|
||||
item,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
else if (response.code === -101) {
|
||||
@@ -127,6 +145,7 @@ async function getFollowedUsersVideos() {
|
||||
}
|
||||
}
|
||||
finally {
|
||||
videoList.value = videoList.value.filter(video => video.item)
|
||||
isLoading.value = false
|
||||
emit('afterLoading')
|
||||
}
|
||||
@@ -159,39 +178,28 @@ defineExpose({ initData })
|
||||
>
|
||||
<VideoCard
|
||||
v-for="video in videoList"
|
||||
:key="video.modules.module_dynamic.major.archive?.aid"
|
||||
:video="{
|
||||
id: Number(video.modules.module_dynamic.major.archive?.aid),
|
||||
durationStr: video.modules.module_dynamic.major.archive?.duration_text,
|
||||
title: `${video.modules.module_dynamic.major.archive?.title}`,
|
||||
cover: `${video.modules.module_dynamic.major.archive?.cover}`,
|
||||
author: video.modules.module_author.name,
|
||||
authorFace: video.modules.module_author.face,
|
||||
mid: video.modules.module_author.mid,
|
||||
viewStr: video.modules.module_dynamic.major.archive?.stat.play,
|
||||
danmakuStr: video.modules.module_dynamic.major.archive?.stat.danmaku,
|
||||
capsuleText: video.modules.module_author.pub_time,
|
||||
bvid: video.modules.module_dynamic.major.archive?.bvid,
|
||||
}"
|
||||
:key="video.uniqueId"
|
||||
:skeleton="!video.item"
|
||||
:video="video.item ? {
|
||||
id: Number(video.item.modules.module_dynamic.major.archive?.aid),
|
||||
durationStr: video.item.modules.module_dynamic.major.archive?.duration_text,
|
||||
title: `${video.item.modules.module_dynamic.major.archive?.title}`,
|
||||
cover: `${video.item.modules.module_dynamic.major.archive?.cover}`,
|
||||
author: video.item.modules.module_author.name,
|
||||
authorFace: video.item.modules.module_author.face,
|
||||
mid: video.item.modules.module_author.mid,
|
||||
viewStr: video.item.modules.module_dynamic.major.archive?.stat.play,
|
||||
danmakuStr: video.item.modules.module_dynamic.major.archive?.stat.danmaku,
|
||||
capsuleText: video.item.modules.module_author.pub_time,
|
||||
bvid: video.item.modules.module_dynamic.major.archive?.bvid,
|
||||
} : undefined"
|
||||
show-preview
|
||||
:horizontal="gridLayout !== 'adaptive'"
|
||||
/>
|
||||
|
||||
<!-- skeleton -->
|
||||
<template v-if="isLoading">
|
||||
<VideoCardSkeleton
|
||||
v-for="item in 30" :key="item"
|
||||
:horizontal="gridLayout !== 'adaptive'"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- no more content -->
|
||||
<Empty v-if="noMoreContent && !needToLoginFirst" class="pb-4" :description="$t('common.no_more_content')" />
|
||||
|
||||
<Transition name="fade">
|
||||
<Loading v-if="isLoading" />
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -6,9 +6,7 @@ import { useToast } from 'vue-toastification'
|
||||
import Button from '~/components/Button.vue'
|
||||
import Dialog from '~/components/Dialog.vue'
|
||||
import Empty from '~/components/Empty.vue'
|
||||
import Loading from '~/components/Loading.vue'
|
||||
import VideoCard from '~/components/VideoCard/VideoCard.vue'
|
||||
import VideoCardSkeleton from '~/components/VideoCard/VideoCardSkeleton.vue'
|
||||
import { useApiClient } from '~/composables/api'
|
||||
import { useBewlyApp } from '~/composables/useAppProvider'
|
||||
import { LanguageType } from '~/enums/appEnums'
|
||||
@@ -20,6 +18,17 @@ import type { forYouResult, Item as VideoItem } from '~/models/video/forYou'
|
||||
import { getTvSign, TVAppKey } from '~/utils/authProvider'
|
||||
import { isVerticalVideo } from '~/utils/uriParse'
|
||||
|
||||
// https://github.com/starknt/BewlyBewly/blob/fad999c2e482095dc3840bb291af53d15ff44130/src/contentScripts/views/Home/components/ForYou.vue#L16
|
||||
interface VideoElement {
|
||||
uniqueId: string
|
||||
item?: VideoItem
|
||||
}
|
||||
|
||||
interface AppVideoElement {
|
||||
uniqueId: string
|
||||
item?: AppVideoItem
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
gridLayout: GridLayout
|
||||
}>()
|
||||
@@ -39,8 +48,8 @@ const gridValue = computed((): string => {
|
||||
|
||||
const toast = useToast()
|
||||
const api = useApiClient()
|
||||
const videoList = reactive<VideoItem[]>([])
|
||||
const appVideoList = reactive<AppVideoItem[]>([])
|
||||
const videoList = ref<VideoElement[]>([])
|
||||
const appVideoList = ref<AppVideoElement[]>([])
|
||||
const isLoading = ref<boolean>(true)
|
||||
const needToLoginFirst = ref<boolean>(false)
|
||||
const containerRef = ref<HTMLElement>() as Ref<HTMLElement>
|
||||
@@ -112,8 +121,8 @@ onActivated(() => {
|
||||
})
|
||||
|
||||
async function initData() {
|
||||
videoList.length = 0
|
||||
appVideoList.length = 0
|
||||
videoList.value.length = 0
|
||||
appVideoList.value.length = 0
|
||||
await getData()
|
||||
}
|
||||
|
||||
@@ -149,6 +158,16 @@ async function getRecommendVideos() {
|
||||
emit('beforeLoading')
|
||||
isLoading.value = true
|
||||
try {
|
||||
let i = 0
|
||||
// https://github.com/starknt/BewlyBewly/blob/fad999c2e482095dc3840bb291af53d15ff44130/src/contentScripts/views/Home/components/ForYou.vue#L208
|
||||
// When video list is not empty, addthe number of pending videos is half of the page size
|
||||
// is set to prevent user scrolling the page too fast and causing the page too laggy
|
||||
const pendingVideos: VideoElement[] = Array.from({ length: videoList.value.length ? pageSize / 2 : pageSize }, () => ({
|
||||
uniqueId: `unique-id-${(videoList.value.length || 0) + i++})}`,
|
||||
} satisfies VideoElement))
|
||||
let lastVideoListLength = videoList.value.length
|
||||
videoList.value.push(...pendingVideos)
|
||||
|
||||
const response: forYouResult = await api.video.getRecommendVideos({
|
||||
fresh_idx: refreshIdx.value++,
|
||||
ps: pageSize,
|
||||
@@ -167,18 +186,25 @@ async function getRecommendVideos() {
|
||||
})
|
||||
|
||||
// when videoList has length property, it means it is the first time to load
|
||||
if (!videoList.length) {
|
||||
Object.assign(videoList, resData)
|
||||
if (!videoList.value.length) {
|
||||
videoList.value = resData.map(item => ({ uniqueId: `${item.id}`, item }))
|
||||
}
|
||||
else {
|
||||
// else we concat the new data to the old data
|
||||
Object.assign(videoList, videoList.concat(resData))
|
||||
resData.forEach((item) => {
|
||||
videoList.value[lastVideoListLength++] = {
|
||||
uniqueId: `${item.id}`,
|
||||
item,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
else if (response.code === 62011) {
|
||||
needToLoginFirst.value = true
|
||||
}
|
||||
}
|
||||
catch {
|
||||
videoList.value = videoList.value.filter(video => video.item)
|
||||
}
|
||||
finally {
|
||||
isLoading.value = false
|
||||
emit('afterLoading')
|
||||
@@ -189,12 +215,23 @@ async function getAppRecommendVideos() {
|
||||
emit('beforeLoading')
|
||||
isLoading.value = true
|
||||
try {
|
||||
let i = 0
|
||||
// https://github.com/starknt/BewlyBewly/blob/fad999c2e482095dc3840bb291af53d15ff44130/src/contentScripts/views/Home/components/ForYou.vue#L208
|
||||
// When video list is not empty, there will be approximately 10 pending videos
|
||||
// due to the app recommendation filtering ad cards.
|
||||
// To prevent user scrolling the page too fast and causing the page too laggy
|
||||
const pendingVideos: AppVideoElement[] = Array.from({ length: appVideoList.value.length ? 10 : pageSize }, () => ({
|
||||
uniqueId: `unique-id-${(appVideoList.value.length || 0) + i++})}`,
|
||||
} satisfies AppVideoElement))
|
||||
let lastVideoListLength = appVideoList.value.length
|
||||
appVideoList.value.push(...pendingVideos)
|
||||
|
||||
const response: AppForYouResult = await api.video.getAppRecommendVideos({
|
||||
access_key: accessKey.value,
|
||||
s_locale: settings.value.language === LanguageType.Mandarin_TW || settings.value.language === LanguageType.Cantonese ? 'zh-Hant_TW' : 'zh-Hans_CN',
|
||||
c_locate: settings.value.language === LanguageType.Mandarin_TW || settings.value.language === LanguageType.Cantonese ? 'zh-Hant_TW' : 'zh-Hans_CN',
|
||||
appkey: TVAppKey.appkey,
|
||||
idx: appVideoList.length > 0 ? appVideoList[appVideoList.length - 1].idx : 1,
|
||||
idx: appVideoList.value.length > 0 ? appVideoList.value[appVideoList.value.length - 1].item?.idx : 1,
|
||||
})
|
||||
|
||||
if (response.code === 0) {
|
||||
@@ -207,12 +244,16 @@ async function getAppRecommendVideos() {
|
||||
})
|
||||
|
||||
// when videoList has length property, it means it is the first time to load
|
||||
if (!appVideoList.length) {
|
||||
Object.assign(appVideoList, resData)
|
||||
if (!appVideoList.value.length) {
|
||||
appVideoList.value = resData.map(item => ({ uniqueId: `${item.idx}`, item }))
|
||||
}
|
||||
else {
|
||||
// else we concat the new data to the old data
|
||||
Object.assign(appVideoList, appVideoList.concat(resData))
|
||||
resData.forEach((item) => {
|
||||
appVideoList.value[lastVideoListLength++] = {
|
||||
uniqueId: `${item.idx}`,
|
||||
item,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
else if (response.code === 62011) {
|
||||
@@ -220,6 +261,9 @@ async function getAppRecommendVideos() {
|
||||
}
|
||||
}
|
||||
finally {
|
||||
// Since the video list in app recommendation mode will filter the ad cards,
|
||||
// after loading, the video list will be filtered again to remove the empty cards
|
||||
appVideoList.value = appVideoList.value.filter(video => video.item)
|
||||
isLoading.value = false
|
||||
emit('afterLoading')
|
||||
}
|
||||
@@ -358,7 +402,7 @@ function handleAppUndoDislike(video: AppVideoItem) {
|
||||
})
|
||||
}
|
||||
|
||||
function getVideoUniqueKey(video: VideoItem) {
|
||||
function getVideoUniqueKey(video: VideoItem): string {
|
||||
return video.id + (video.bvid || video.uri || '')
|
||||
}
|
||||
|
||||
@@ -467,82 +511,72 @@ defineExpose({ initData })
|
||||
<template v-if="settings.recommendationMode === 'web'">
|
||||
<VideoCard
|
||||
v-for="video in videoList"
|
||||
:key="video.id"
|
||||
:skeleton="!video.title"
|
||||
:video="{
|
||||
id: video.id,
|
||||
duration: video.duration,
|
||||
title: video.title,
|
||||
cover: video.pic,
|
||||
author: video.owner.name,
|
||||
authorFace: video.owner.face,
|
||||
followed: !!video.is_followed,
|
||||
mid: video.owner.mid,
|
||||
view: video.stat.view,
|
||||
danmaku: video.stat.danmaku,
|
||||
publishedTimestamp: video.pubdate,
|
||||
bvid: video.bvid,
|
||||
cid: video.cid,
|
||||
url: video.uri,
|
||||
}"
|
||||
:key="video.uniqueId"
|
||||
:skeleton="!video.item"
|
||||
:video="video.item ? {
|
||||
id: video.item.id,
|
||||
duration: video.item.duration,
|
||||
title: video.item.title,
|
||||
cover: video.item.pic,
|
||||
author: video.item.owner.name,
|
||||
authorFace: video.item.owner.face,
|
||||
followed: !!video.item.is_followed,
|
||||
mid: video.item.owner.mid,
|
||||
view: video.item.stat.view,
|
||||
danmaku: video.item.stat.danmaku,
|
||||
publishedTimestamp: video.item.pubdate,
|
||||
bvid: video.item.bvid,
|
||||
cid: video.item.cid,
|
||||
} : undefined"
|
||||
show-preview
|
||||
:horizontal="gridLayout !== 'adaptive'"
|
||||
more-btn
|
||||
:more-btn-active="video.id === activatedVideoId"
|
||||
:removed="dislikedVideoUniqueKeys.includes(getVideoUniqueKey(video))"
|
||||
@more-click="(e) => handleMoreClick(e, video)"
|
||||
@undo="handleUndoDislike(video)"
|
||||
:more-btn-active="video.item && video.item.id === activatedVideoId"
|
||||
:removed="video.item && dislikedVideoUniqueKeys.includes(getVideoUniqueKey(video.item))"
|
||||
@more-click="(e) => handleMoreClick(e, video.item!)"
|
||||
@undo="handleUndoDislike(video.item!)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<VideoCard
|
||||
v-for="video in appVideoList"
|
||||
:key="video.args.aid"
|
||||
:key="video.uniqueId"
|
||||
ref="videoCardRef"
|
||||
:skeleton="!video"
|
||||
:video="{
|
||||
id: video.args.aid ?? 0,
|
||||
durationStr: video.cover_right_text,
|
||||
title: `${video.title}`,
|
||||
cover: `${video.cover}`,
|
||||
author: video?.mask?.avatar.text,
|
||||
authorFace: video?.mask?.avatar.cover,
|
||||
followed: video?.bottom_rcmd_reason === '已关注' || video?.bottom_rcmd_reason === '已關注',
|
||||
mid: video?.mask?.avatar.up_id,
|
||||
capsuleText: video?.desc?.split('·')[1],
|
||||
bvid: video.bvid,
|
||||
viewStr: video.cover_left_text_1,
|
||||
danmakuStr: video.cover_left_text_2,
|
||||
cid: video?.player_args?.cid,
|
||||
url: video.uri,
|
||||
type: video.card_goto === 'bangumi' ? 'bangumi' : isVerticalVideo(video.uri!) ? 'vertical' : 'horizontal',
|
||||
}"
|
||||
:skeleton="!video.item"
|
||||
:video="video.item ? {
|
||||
id: video.item.args.aid ?? 0,
|
||||
durationStr: video.item.cover_right_text,
|
||||
title: `${video.item.title}`,
|
||||
cover: `${video.item.cover}`,
|
||||
author: video.item?.mask?.avatar.text,
|
||||
authorFace: video.item?.mask?.avatar.cover,
|
||||
followed: video.item?.bottom_rcmd_reason === '已关注' || video.item?.bottom_rcmd_reason === '已關注',
|
||||
mid: video.item?.mask?.avatar.up_id,
|
||||
capsuleText: video.item?.desc?.split('·')[1],
|
||||
bvid: video.item.bvid,
|
||||
viewStr: video.item.cover_left_text_1,
|
||||
danmakuStr: video.item.cover_left_text_2,
|
||||
cid: video.item?.player_args?.cid,
|
||||
type: video.item.card_goto === 'bangumi' ? 'bangumi' : isVerticalVideo(video.item.uri!) ? 'vertical' : 'horizontal',
|
||||
} : undefined"
|
||||
show-preview
|
||||
:horizontal="gridLayout !== 'adaptive'"
|
||||
more-btn
|
||||
:more-btn-active="video.idx === activatedAppVideoIdx"
|
||||
:removed="dislikedAppVideoUniqueKeys.includes(getAppVideoUniqueKey(video))"
|
||||
@more-click="(e) => handleAppMoreClick(e, video)"
|
||||
@undo="handleAppUndoDislike(video)"
|
||||
:more-btn-active="video.item && video.item.idx === activatedAppVideoIdx"
|
||||
:removed="video.item && dislikedAppVideoUniqueKeys.includes(getAppVideoUniqueKey(video.item))"
|
||||
@more-click="(e) => handleAppMoreClick(e, video.item!)"
|
||||
@undo="handleAppUndoDislike(video.item!)"
|
||||
/>
|
||||
<!-- :more-options="video.three_point_v2" -->
|
||||
</template>
|
||||
|
||||
<!-- skeleton -->
|
||||
<template v-if="isLoading">
|
||||
<VideoCardSkeleton
|
||||
v-for="item in 30" :key="item"
|
||||
:horizontal="gridLayout !== 'adaptive'"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- no more content -->
|
||||
<Empty v-if="noMoreContent" class="pb-4" :description="$t('common.no_more_content')" />
|
||||
|
||||
<Transition name="fade">
|
||||
<Loading v-if="isLoading" />
|
||||
</Transition>
|
||||
<!-- <Transition name="fade">
|
||||
<Loading v-show="isLoading" />
|
||||
</Transition> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -3,14 +3,18 @@ import type { Ref } from 'vue'
|
||||
|
||||
import Button from '~/components/Button.vue'
|
||||
import Empty from '~/components/Empty.vue'
|
||||
import Loading from '~/components/Loading.vue'
|
||||
import VideoCard from '~/components/VideoCard/VideoCard.vue'
|
||||
import VideoCardSkeleton from '~/components/VideoCard/VideoCardSkeleton.vue'
|
||||
import { useApiClient } from '~/composables/api'
|
||||
import { useBewlyApp } from '~/composables/useAppProvider'
|
||||
import type { GridLayout } from '~/logic'
|
||||
import type { DataItem as MomentItem, MomentResult } from '~/models/moment/moment'
|
||||
|
||||
// https://github.com/starknt/BewlyBewly/blob/fad999c2e482095dc3840bb291af53d15ff44130/src/contentScripts/views/Home/components/ForYou.vue#L16
|
||||
interface VideoElement {
|
||||
uniqueId: string
|
||||
item?: MomentItem
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
gridLayout: GridLayout
|
||||
}>()
|
||||
@@ -27,8 +31,10 @@ const gridValue = computed((): string => {
|
||||
return '~ cols-1 xl:cols-2 gap-4'
|
||||
return '~ cols-1 gap-4'
|
||||
})
|
||||
|
||||
const api = useApiClient()
|
||||
const momentList = reactive<MomentItem[]>([])
|
||||
|
||||
const videoList = ref<VideoElement[]>([])
|
||||
const isLoading = ref<boolean>(false)
|
||||
const needToLoginFirst = ref<boolean>(false)
|
||||
const containerRef = ref<HTMLElement>() as Ref<HTMLElement>
|
||||
@@ -50,7 +56,7 @@ onActivated(() => {
|
||||
async function initData() {
|
||||
offset.value = ''
|
||||
updateBaseline.value = ''
|
||||
momentList.length = 0
|
||||
videoList.value.length = 0
|
||||
noMoreContent.value = false
|
||||
noMoreContentWarning.value = false
|
||||
|
||||
@@ -93,6 +99,16 @@ async function getFollowedUsersVideos() {
|
||||
emit('beforeLoading')
|
||||
isLoading.value = true
|
||||
try {
|
||||
let i = 0
|
||||
// https://github.com/starknt/BewlyBewly/blob/fad999c2e482095dc3840bb291af53d15ff44130/src/contentScripts/views/Home/components/ForYou.vue#L208
|
||||
// When video list is not empty, addthe number of pending videos is half of the page size
|
||||
// is set to prevent user scrolling the page too fast and causing the page too laggy
|
||||
const pendingVideos: VideoElement[] = Array.from({ length: videoList.value.length ? 10 : 30 }, () => ({
|
||||
uniqueId: `unique-id-${(videoList.value.length || 0) + i++})}`,
|
||||
} satisfies VideoElement))
|
||||
let lastVideoListLength = videoList.value.length
|
||||
videoList.value.push(...pendingVideos)
|
||||
|
||||
const response: MomentResult = await api.moment.getMoments({
|
||||
type: 'pgc',
|
||||
offset: Number(offset.value),
|
||||
@@ -116,12 +132,16 @@ async function getFollowedUsersVideos() {
|
||||
})
|
||||
|
||||
// when videoList has length property, it means it is the first time to load
|
||||
if (!momentList.length) {
|
||||
Object.assign(momentList, resData)
|
||||
if (!videoList.value.length) {
|
||||
videoList.value = resData.map(item => ({ uniqueId: `${item.id_str}`, item }))
|
||||
}
|
||||
else {
|
||||
// else we concat the new data to the old data
|
||||
Object.assign(momentList, momentList.concat(resData))
|
||||
resData.forEach((item) => {
|
||||
videoList.value[lastVideoListLength++] = {
|
||||
uniqueId: `${item.id_str}`,
|
||||
item,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
else if (response.code === -101) {
|
||||
@@ -129,6 +149,7 @@ async function getFollowedUsersVideos() {
|
||||
}
|
||||
}
|
||||
finally {
|
||||
videoList.value = videoList.value.filter(video => video.item)
|
||||
isLoading.value = false
|
||||
emit('afterLoading')
|
||||
}
|
||||
@@ -160,40 +181,29 @@ defineExpose({ initData })
|
||||
:grid="gridValue"
|
||||
>
|
||||
<VideoCard
|
||||
v-for="moment in momentList"
|
||||
:key="moment.modules.module_author.mid"
|
||||
:video="{
|
||||
id: moment.modules.module_author.mid,
|
||||
title: `${moment.modules.module_dynamic.major.pgc?.title}`,
|
||||
cover: `${moment.modules.module_dynamic.major.pgc?.cover}`,
|
||||
author: moment.modules.module_author.name,
|
||||
authorFace: moment.modules.module_author.face,
|
||||
mid: moment.modules.module_author.mid,
|
||||
authorUrl: moment.modules.module_author.jump_url,
|
||||
viewStr: moment.modules.module_dynamic.major.pgc?.stat.play,
|
||||
danmakuStr: moment.modules.module_dynamic.major.pgc?.stat.danmaku,
|
||||
capsuleText: moment.modules.module_author.pub_time,
|
||||
epid: moment.modules.module_dynamic.major.pgc?.epid,
|
||||
}"
|
||||
v-for="video in videoList"
|
||||
:key="video.uniqueId"
|
||||
:skeleton="!video.item"
|
||||
:video="video.item ? {
|
||||
id: video.item.modules.module_author.mid,
|
||||
title: `${video.item.modules.module_dynamic.major.pgc?.title}`,
|
||||
cover: `${video.item.modules.module_dynamic.major.pgc?.cover}`,
|
||||
author: video.item.modules.module_author.name,
|
||||
authorFace: video.item.modules.module_author.face,
|
||||
mid: video.item.modules.module_author.mid,
|
||||
authorUrl: video.item.modules.module_author.jump_url,
|
||||
viewStr: video.item.modules.module_dynamic.major.pgc?.stat.play,
|
||||
danmakuStr: video.item.modules.module_dynamic.major.pgc?.stat.danmaku,
|
||||
capsuleText: video.item.modules.module_author.pub_time,
|
||||
epid: video.item.modules.module_dynamic.major.pgc?.epid,
|
||||
} : undefined"
|
||||
:show-watcher-later="false"
|
||||
:horizontal="gridLayout !== 'adaptive'"
|
||||
/>
|
||||
|
||||
<!-- skeleton -->
|
||||
<template v-if="isLoading">
|
||||
<VideoCardSkeleton
|
||||
v-for="item in 30" :key="item"
|
||||
:horizontal="gridLayout !== 'adaptive'"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- no more content -->
|
||||
<Empty v-if="noMoreContentWarning" class="pb-4" :description="$t('common.no_more_content')" />
|
||||
|
||||
<Transition name="fade">
|
||||
<Loading v-if="isLoading" />
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
import Loading from '~/components/Loading.vue'
|
||||
import VideoCard from '~/components/VideoCard/VideoCard.vue'
|
||||
import VideoCardSkeleton from '~/components/VideoCard/VideoCardSkeleton.vue'
|
||||
import { useApiClient } from '~/composables/api'
|
||||
import { useBewlyApp } from '~/composables/useAppProvider'
|
||||
import type { GridLayout } from '~/logic'
|
||||
import type { List as VideoItem, TrendingResult } from '~/models/video/trending'
|
||||
|
||||
// https://github.com/starknt/BewlyBewly/blob/fad999c2e482095dc3840bb291af53d15ff44130/src/contentScripts/views/Home/components/ForYou.vue#L16
|
||||
interface VideoElement {
|
||||
uniqueId: string
|
||||
item?: VideoItem
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
gridLayout: GridLayout
|
||||
}>()
|
||||
@@ -26,7 +30,7 @@ const gridValue = computed((): string => {
|
||||
return '~ cols-1 gap-4'
|
||||
})
|
||||
const api = useApiClient()
|
||||
const videoList = reactive<VideoItem[]>([])
|
||||
const videoList = ref<VideoElement[]>([])
|
||||
const isLoading = ref<boolean>(false)
|
||||
const containerRef = ref<HTMLElement>() as Ref<HTMLElement>
|
||||
const pn = ref<number>(1)
|
||||
@@ -44,7 +48,7 @@ onActivated(() => {
|
||||
|
||||
async function initData() {
|
||||
noMoreContent.value = false
|
||||
videoList.length = 0
|
||||
videoList.value.length = 0
|
||||
pn.value = 1
|
||||
await getData()
|
||||
}
|
||||
@@ -71,6 +75,16 @@ async function getTrendingVideos() {
|
||||
emit('beforeLoading')
|
||||
isLoading.value = true
|
||||
try {
|
||||
let i = 0
|
||||
// https://github.com/starknt/BewlyBewly/blob/fad999c2e482095dc3840bb291af53d15ff44130/src/contentScripts/views/Home/components/ForYou.vue#L208
|
||||
// When video list is not empty, addthe number of pending videos is half of the page size
|
||||
// is set to prevent user scrolling the page too fast and causing the page too laggy
|
||||
const pendingVideos: VideoElement[] = Array.from({ length: videoList.value.length ? 10 : 30 }, () => ({
|
||||
uniqueId: `unique-id-${(videoList.value.length || 0) + i++})}`,
|
||||
} satisfies VideoElement))
|
||||
let lastVideoListLength = videoList.value.length
|
||||
videoList.value.push(...pendingVideos)
|
||||
|
||||
const response: TrendingResult = await api.video.getPopularVideos({
|
||||
pn: pn.value++,
|
||||
ps: 30,
|
||||
@@ -86,16 +100,21 @@ async function getTrendingVideos() {
|
||||
})
|
||||
|
||||
// when videoList has length property, it means it is the first time to load
|
||||
if (!videoList.length) {
|
||||
Object.assign(videoList, resData)
|
||||
if (!videoList.value.length) {
|
||||
videoList.value = resData.map(item => ({ uniqueId: `${item.aid}`, item }))
|
||||
}
|
||||
else {
|
||||
// else we concat the new data to the old data
|
||||
Object.assign(videoList, videoList.concat(resData))
|
||||
resData.forEach((item) => {
|
||||
videoList.value[lastVideoListLength++] = {
|
||||
uniqueId: `${item.aid}`,
|
||||
item,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
videoList.value = videoList.value.filter(video => video.item)
|
||||
isLoading.value = false
|
||||
emit('afterLoading')
|
||||
}
|
||||
@@ -118,39 +137,28 @@ defineExpose({ initData })
|
||||
>
|
||||
<VideoCard
|
||||
v-for="video in videoList"
|
||||
:key="video.aid"
|
||||
:video="{
|
||||
id: Number(video.aid),
|
||||
duration: video.duration,
|
||||
title: video.title,
|
||||
desc: video.desc,
|
||||
cover: video.pic,
|
||||
author: video.owner.name,
|
||||
authorFace: video.owner.face,
|
||||
mid: video.owner.mid,
|
||||
view: video.stat.view,
|
||||
danmaku: video.stat.danmaku,
|
||||
publishedTimestamp: video.pubdate,
|
||||
bvid: video.bvid,
|
||||
tag: video.rcmd_reason.content,
|
||||
cid: video.cid,
|
||||
}"
|
||||
:key="video.uniqueId"
|
||||
:skeleton="!video.item"
|
||||
:video="video.item ? {
|
||||
id: Number(video.item.aid),
|
||||
duration: video.item.duration,
|
||||
title: video.item.title,
|
||||
desc: video.item.desc,
|
||||
cover: video.item.pic,
|
||||
author: video.item.owner.name,
|
||||
authorFace: video.item.owner.face,
|
||||
mid: video.item.owner.mid,
|
||||
view: video.item.stat.view,
|
||||
danmaku: video.item.stat.danmaku,
|
||||
publishedTimestamp: video.item.pubdate,
|
||||
bvid: video.item.bvid,
|
||||
tag: video.item.rcmd_reason.content,
|
||||
cid: video.item.cid,
|
||||
} : undefined"
|
||||
show-preview
|
||||
:horizontal="gridLayout !== 'adaptive'"
|
||||
/>
|
||||
|
||||
<!-- skeleton -->
|
||||
<template v-if="isLoading">
|
||||
<VideoCardSkeleton
|
||||
v-for="item in 30" :key="item"
|
||||
:horizontal="gridLayout !== 'adaptive'"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<Transition name="fade">
|
||||
<Loading v-if="isLoading" />
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user