mirror of
https://github.com/BewlyBewly/BewlyBewly.git
synced 2025-04-14 13:15:29 +00:00
feat(Home): implement grid layout switcher
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue'
|
||||
import type { GridLayoutIcon } from './types'
|
||||
import { HomeSubPage } from './types'
|
||||
import emitter from '~/utils/mitt'
|
||||
import { settings } from '~/logic'
|
||||
import { homePageGridLayout, settings } from '~/logic'
|
||||
import { delay } from '~/utils/main'
|
||||
import type { HomeTab } from '~/stores/mainStore'
|
||||
import { useMainStore } from '~/stores/mainStore'
|
||||
@@ -21,9 +22,16 @@ const pages = {
|
||||
const showSearchPageMode = ref<boolean>(false)
|
||||
const shouldMoveTabsUp = ref<boolean>(false)
|
||||
const tabContentLoading = ref<boolean>(false)
|
||||
|
||||
const currentTabs = ref<HomeTab[]>([])
|
||||
|
||||
const gridLayoutIcons = computed((): GridLayoutIcon[] => {
|
||||
return [
|
||||
{ icon: 'f7:square-grid-3x2', iconActivated: 'f7:square-grid-3x2-fill', value: 'adaptive' },
|
||||
{ icon: 'f7:rectangle-grid-2x2', iconActivated: 'f7:rectangle-grid-2x2-fill', value: 'twoColumns' },
|
||||
{ icon: 'f7:rectangle-grid-1x2', iconActivated: 'f7:rectangle-grid-1x2-fill', value: 'oneColumn' },
|
||||
]
|
||||
})
|
||||
|
||||
watch(() => activatedPage.value, () => {
|
||||
handleBackToTop(settings.value.useSearchPageModeOnHomePage ? 510 : 0)
|
||||
})
|
||||
@@ -171,8 +179,8 @@ function toggleTabContentLoading(loading: boolean) {
|
||||
</Transition>
|
||||
|
||||
<header
|
||||
pos="sticky top-80px" w-fit z-9 mb-9 duration-300
|
||||
ease-in-out
|
||||
pos="sticky top-80px" w-full z-9 mb-9 duration-300
|
||||
ease-in-out flex="~ justify-between items-center gap-4"
|
||||
:class="{ hide: shouldMoveTabsUp }"
|
||||
>
|
||||
<ul flex="~ items-center gap-3 wrap">
|
||||
@@ -196,12 +204,31 @@ function toggleTabContentLoading(loading: boolean) {
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div
|
||||
style="backdrop-filter: var(--bew-filter-glass-1)"
|
||||
flex="~ gap-1" p-1 h-35px bg="$bew-elevated-1"
|
||||
rounded="$bew-radius" shadow="$bew-shadow-1" box-border border="1 $bew-border-color"
|
||||
>
|
||||
<Icon
|
||||
v-for="icon in gridLayoutIcons" :key="icon.value"
|
||||
:icon="homePageGridLayout === icon.value ? icon.iconActivated : icon.icon"
|
||||
:style="{
|
||||
backgroundColor: homePageGridLayout === icon.value ? 'var(--bew-theme-color-auto)' : '',
|
||||
color: homePageGridLayout === icon.value ? 'var(--bew-text-auto)' : 'unset',
|
||||
}"
|
||||
w-full
|
||||
h-full p="x-2 y-1" rounded="$bew-radius-half" bg="hover:$bew-fill-2" duration-300
|
||||
cursor-pointer @click="homePageGridLayout = icon.value"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<Transition name="page-fade">
|
||||
<KeepAlive include="ForYou">
|
||||
<Component
|
||||
:is="pages[activatedPage]" :key="activatedPage"
|
||||
:grid-layout="homePageGridLayout"
|
||||
@before-loading="toggleTabContentLoading(true)"
|
||||
@after-loading="toggleTabContentLoading(false)"
|
||||
/>
|
||||
@@ -242,7 +269,7 @@ function toggleTabContentLoading(loading: boolean) {
|
||||
}
|
||||
|
||||
.tab-activated {
|
||||
--at-apply: bg-$bew-theme-color dark:bg-white color-white dark:color-black
|
||||
--at-apply: bg-$bew-theme-color-auto text-$bew-text-auto
|
||||
border-$bew-theme-color dark:border-white;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import type { Ref } from 'vue'
|
||||
import type { GridLayout } from '~/logic'
|
||||
import type { DataItem as MomentItem, MomentResult } from '~/models/moment/moment'
|
||||
|
||||
const props = defineProps<{
|
||||
gridLayout: GridLayout
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'beforeLoading'): void
|
||||
(e: 'afterLoading'): void
|
||||
}>()
|
||||
|
||||
const gridValue = computed((): string => {
|
||||
if (props.gridLayout === 'adaptive')
|
||||
return '~ 2xl:cols-5 xl:cols-4 lg:cols-3 md:cols-2 gap-5'
|
||||
if (props.gridLayout === 'twoColumns')
|
||||
return '~ cols-1 xl:cols-2 gap-4'
|
||||
return '~ cols-1 gap-4'
|
||||
})
|
||||
|
||||
const videoList = reactive<MomentItem[]>([])
|
||||
const isLoading = ref<boolean>(false)
|
||||
const needToLoginFirst = ref<boolean>(false)
|
||||
@@ -111,6 +124,11 @@ function jumpToLoginPage() {
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- By directly using predefined unocss grid properties, it is possible to dynamically set the grid attribute -->
|
||||
<div hidden grid="~ 2xl:cols-5 xl:cols-4 lg:cols-3 md:cols-2 gap-5" />
|
||||
<div hidden grid="~ cols-1 xl:cols-2 gap-4" />
|
||||
<div hidden grid="~ cols-1 gap-4" />
|
||||
|
||||
<Empty v-if="needToLoginFirst" mt-6 :description="$t('common.please_log_in_first')">
|
||||
<Button type="primary" @click="jumpToLoginPage()">
|
||||
{{ $t('common.login') }}
|
||||
@@ -120,7 +138,7 @@ function jumpToLoginPage() {
|
||||
v-else
|
||||
ref="containerRef"
|
||||
m="b-0 t-0" relative w-full h-full
|
||||
grid="~ 2xl:cols-5 xl:cols-4 lg:cols-3 md:cols-2 gap-5"
|
||||
:grid="gridValue"
|
||||
>
|
||||
<VideoCard
|
||||
v-for="video in videoList"
|
||||
@@ -137,11 +155,15 @@ function jumpToLoginPage() {
|
||||
:capsule-text="video.modules.module_author.pub_time"
|
||||
:bvid="video.modules.module_dynamic.major.archive?.bvid"
|
||||
show-preview
|
||||
:horizontal="gridLayout !== 'adaptive'"
|
||||
/>
|
||||
|
||||
<!-- skeleton -->
|
||||
<template v-if="isLoading">
|
||||
<VideoCardSkeleton v-for="item in 30" :key="item" />
|
||||
<VideoCardSkeleton
|
||||
v-for="item in 30" :key="item"
|
||||
:horizontal="gridLayout !== 'adaptive'"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,15 +2,28 @@
|
||||
import type { Ref } from 'vue'
|
||||
import type { AppForYouResult, Item as AppVideoItem } from '~/models/video/appForYou'
|
||||
import type { Item as VideoItem, forYouResult } from '~/models/video/forYou'
|
||||
import type { GridLayout } from '~/logic'
|
||||
import { accessKey, settings } from '~/logic'
|
||||
import { LanguageType } from '~/enums/appEnums'
|
||||
import { TVAppKey } from '~/utils/authProvider'
|
||||
|
||||
const props = defineProps<{
|
||||
gridLayout: GridLayout
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'beforeLoading'): void
|
||||
(e: 'afterLoading'): void
|
||||
}>()
|
||||
|
||||
const gridValue = computed((): string => {
|
||||
if (props.gridLayout === 'adaptive')
|
||||
return '~ 2xl:cols-5 xl:cols-4 lg:cols-3 md:cols-2 gap-5'
|
||||
if (props.gridLayout === 'twoColumns')
|
||||
return '~ cols-1 xl:cols-2 gap-4'
|
||||
return '~ cols-1 gap-4'
|
||||
})
|
||||
|
||||
const videoList = reactive<VideoItem[]>([])
|
||||
const appVideoList = reactive<AppVideoItem[]>([])
|
||||
const isLoading = ref<boolean>(true)
|
||||
@@ -174,16 +187,22 @@ function jumpToLoginPage() {
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- By directly using predefined unocss grid properties, it is possible to dynamically set the grid attribute -->
|
||||
<div hidden grid="~ 2xl:cols-5 xl:cols-4 lg:cols-3 md:cols-2 gap-5" />
|
||||
<div hidden grid="~ cols-1 xl:cols-2 gap-4" />
|
||||
<div hidden grid="~ cols-1 gap-4" />
|
||||
|
||||
<Empty v-if="needToLoginFirst" mt-6 :description="$t('common.please_log_in_first')">
|
||||
<Button type="primary" @click="jumpToLoginPage()">
|
||||
{{ $t('common.login') }}
|
||||
</Button>
|
||||
</Empty>
|
||||
|
||||
<div
|
||||
v-else
|
||||
ref="containerRef"
|
||||
m="b-0 t-0" relative w-full h-full
|
||||
grid="~ 2xl:cols-5 xl:cols-4 lg:cols-3 md:cols-2 gap-5"
|
||||
:grid="gridValue"
|
||||
>
|
||||
<template v-if="settings.recommendationMode === 'web'">
|
||||
<VideoCard
|
||||
@@ -203,6 +222,7 @@ function jumpToLoginPage() {
|
||||
:cid="video.cid"
|
||||
:uri="video.uri"
|
||||
show-preview
|
||||
:horizontal="gridLayout !== 'adaptive'"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
@@ -223,12 +243,16 @@ function jumpToLoginPage() {
|
||||
:cid="video?.player_args?.cid"
|
||||
:uri="video.uri"
|
||||
show-preview
|
||||
:horizontal="gridLayout !== 'adaptive'"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- skeleton -->
|
||||
<template v-if="isLoading">
|
||||
<VideoCardSkeleton v-for="item in 30" :key="item" />
|
||||
<VideoCardSkeleton
|
||||
v-for="item in 30" :key="item"
|
||||
:horizontal="gridLayout !== 'adaptive'"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,9 +3,14 @@ import { useI18n } from 'vue-i18n'
|
||||
import type { RankingType } from '../types'
|
||||
import type { RankingResult, List as RankingVideoItem } from '~/models/video/ranking'
|
||||
import type { List as RankingPgcItem, RankingPgcResult } from '~/models/video/rankingPgc'
|
||||
import type { GridLayout } from '~/logic'
|
||||
import { settings } from '~/logic'
|
||||
import emitter from '~/utils/mitt'
|
||||
|
||||
const props = defineProps<{
|
||||
gridLayout: GridLayout
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'beforeLoading'): void
|
||||
(e: 'afterLoading'): void
|
||||
@@ -14,6 +19,20 @@ const emit = defineEmits<{
|
||||
const { t } = useI18n()
|
||||
const { handleBackToTop, handlePageRefresh } = useBewlyApp()
|
||||
|
||||
const gridValue = computed((): string => {
|
||||
if (props.gridLayout === 'adaptive') {
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
if (!activatedRankingType.value.seasonType)
|
||||
return '~ 2xl:cols-4 xl:cols-3 lg:cols-2 md:cols-1 gap-5'
|
||||
else
|
||||
return '~ 2xl:cols-5 xl:cols-4 lg:cols-3 md:cols-2 gap-5'
|
||||
}
|
||||
|
||||
if (props.gridLayout === 'twoColumns')
|
||||
return '~ cols-1 xl:cols-2 gap-4'
|
||||
return '~ cols-1 gap-4'
|
||||
})
|
||||
|
||||
const rankingTypes = computed((): RankingType[] => {
|
||||
return [
|
||||
{ id: 1, name: t('ranking.all'), rid: 0 },
|
||||
@@ -140,10 +159,10 @@ function getRankingPgc() {
|
||||
<ul flex="~ col gap-2">
|
||||
<li v-for="rankingType in rankingTypes" :key="rankingType.id">
|
||||
<a
|
||||
:class="{ active: activatedRankingType.id === rankingType.id }"
|
||||
px-4 lh-30px h-30px hover:bg="$bew-fill-2" w-inherit
|
||||
block rounded="$bew-radius" cursor-pointer transition="all 300 ease-out"
|
||||
hover:scale-105 un-text="$bew-text-2 hover:$bew-text-1"
|
||||
:class="{ active: activatedRankingType.id === rankingType.id }"
|
||||
hover:scale-105 un-text="$bew-text-1"
|
||||
@click="activatedRankingType = rankingType"
|
||||
>{{ rankingType.name }}</a>
|
||||
</li>
|
||||
@@ -151,7 +170,13 @@ function getRankingPgc() {
|
||||
</OverlayScrollbarsComponent>
|
||||
</aside>
|
||||
|
||||
<main w-full>
|
||||
<!-- By directly using predefined unocss grid properties, it is possible to dynamically set the grid attribute -->
|
||||
<div hidden grid="~ 2xl:cols-5 xl:cols-4 lg:cols-3 md:cols-2 gap-5" />
|
||||
<div hidden grid="~ 2xl:cols-4 xl:cols-3 lg:cols-2 md:cols-1 gap-5" />
|
||||
<div hidden grid="~ cols-1 xl:cols-2 gap-4" />
|
||||
<div hidden grid="~ cols-1 gap-4" />
|
||||
|
||||
<main w-full :grid="gridValue">
|
||||
<template v-if="!('seasonType' in activatedRankingType)">
|
||||
<VideoCard
|
||||
v-for="(video, index) in videoList"
|
||||
@@ -171,38 +196,41 @@ function getRankingPgc() {
|
||||
:rank="index + 1"
|
||||
:cid="video.cid"
|
||||
show-preview
|
||||
horizontal
|
||||
:horizontal="gridLayout !== 'adaptive'"
|
||||
|
||||
w-full
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div grid="~ cols-2 gap-4">
|
||||
<LongCoverCard
|
||||
v-for="pgc in PgcList"
|
||||
:key="pgc.url"
|
||||
:url="pgc.url"
|
||||
:cover="pgc.cover"
|
||||
:title="pgc.title"
|
||||
:desc="pgc.new_ep.index_show"
|
||||
:view="pgc.stat.view"
|
||||
:follow="pgc.stat.follow"
|
||||
:rank="pgc.rank"
|
||||
:capsule-text="pgc.rating.replace('分', '')"
|
||||
horizontal
|
||||
mb-8
|
||||
/>
|
||||
</div>
|
||||
<LongCoverCard
|
||||
v-for="pgc in PgcList"
|
||||
:key="pgc.url"
|
||||
:url="pgc.url"
|
||||
:cover="pgc.cover"
|
||||
:title="pgc.title"
|
||||
:desc="pgc.new_ep.index_show"
|
||||
:view="pgc.stat.view"
|
||||
:follow="pgc.stat.follow"
|
||||
:rank="pgc.rank"
|
||||
:capsule-text="pgc.rating.replace('分', '')"
|
||||
:horizontal="gridLayout !== 'adaptive'"
|
||||
mb-8
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- skeleton -->
|
||||
<template v-if="isLoading">
|
||||
<template v-if="!('seasonType' in activatedRankingType)">
|
||||
<VideoCardSkeleton v-for="item in 30" :key="item" horizontal />
|
||||
<VideoCardSkeleton
|
||||
v-for="item in 30" :key="item"
|
||||
:horizontal="gridLayout !== 'adaptive'"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div grid="~ cols-2 gap-4">
|
||||
<LongCoverCardSkeleton v-for="item in 30" :key="item" horizontal />
|
||||
</div>
|
||||
<LongCoverCardSkeleton
|
||||
v-for="item in 30" :key="item"
|
||||
:horizontal="gridLayout !== 'adaptive'"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</main>
|
||||
@@ -211,7 +239,7 @@ function getRankingPgc() {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.active {
|
||||
--at-apply: scale-110 bg-$bew-theme-color dark:bg-white text-white dark:text-black shadow-$bew-shadow-2;
|
||||
--at-apply: scale-110 bg-$bew-theme-color-auto text-$bew-text-auto shadow-$bew-shadow-2;
|
||||
}
|
||||
|
||||
.hide {
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import type { Ref } from 'vue'
|
||||
import type { GridLayout } from '~/logic'
|
||||
import type { DataItem as MomentItem, MomentResult } from '~/models/moment/moment'
|
||||
|
||||
const props = defineProps<{
|
||||
gridLayout: GridLayout
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'beforeLoading'): void
|
||||
(e: 'afterLoading'): void
|
||||
}>()
|
||||
|
||||
const gridValue = computed((): string => {
|
||||
if (props.gridLayout === 'adaptive')
|
||||
return '~ 2xl:cols-5 xl:cols-4 lg:cols-3 md:cols-2 gap-5'
|
||||
if (props.gridLayout === 'twoColumns')
|
||||
return '~ cols-1 xl:cols-2 gap-4'
|
||||
return '~ cols-1 gap-4'
|
||||
})
|
||||
|
||||
const momentList = reactive<MomentItem[]>([])
|
||||
const isLoading = ref<boolean>(false)
|
||||
const needToLoginFirst = ref<boolean>(false)
|
||||
@@ -116,6 +129,11 @@ function jumpToLoginPage() {
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- By directly using predefined unocss grid properties, it is possible to dynamically set the grid attribute -->
|
||||
<div hidden grid="~ 2xl:cols-5 xl:cols-4 lg:cols-3 md:cols-2 gap-5" />
|
||||
<div hidden grid="~ cols-1 xl:cols-2 gap-4" />
|
||||
<div hidden grid="~ cols-1 gap-4" />
|
||||
|
||||
<Empty v-if="needToLoginFirst" mt-6 :description="$t('common.please_log_in_first')">
|
||||
<Button type="primary" @click="jumpToLoginPage()">
|
||||
{{ $t('common.login') }}
|
||||
@@ -125,7 +143,7 @@ function jumpToLoginPage() {
|
||||
v-else
|
||||
ref="containerRef"
|
||||
m="b-0 t-0" relative w-full h-full
|
||||
grid="~ 2xl:cols-5 xl:cols-4 lg:cols-3 md:cols-2 gap-5"
|
||||
:grid="gridValue"
|
||||
>
|
||||
<VideoCard
|
||||
v-for="moment in momentList"
|
||||
@@ -142,11 +160,15 @@ function jumpToLoginPage() {
|
||||
:danmaku-str="moment.modules.module_dynamic.major.pgc?.stat.danmaku"
|
||||
:capsule-text="moment.modules.module_author.pub_time"
|
||||
:epid="moment.modules.module_dynamic.major.pgc?.epid"
|
||||
:horizontal="gridLayout !== 'adaptive'"
|
||||
/>
|
||||
|
||||
<!-- skeleton -->
|
||||
<template v-if="isLoading">
|
||||
<VideoCardSkeleton v-for="item in 30" :key="item" />
|
||||
<VideoCardSkeleton
|
||||
v-for="item in 30" :key="item"
|
||||
:horizontal="gridLayout !== 'adaptive'"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import type { Ref } from 'vue'
|
||||
import type { GridLayout } from '~/logic'
|
||||
import type { TrendingResult, List as VideoItem } from '~/models/video/trending'
|
||||
|
||||
const props = defineProps<{
|
||||
gridLayout: GridLayout
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'beforeLoading'): void
|
||||
(e: 'afterLoading'): void
|
||||
}>()
|
||||
|
||||
const gridValue = computed((): string => {
|
||||
if (props.gridLayout === 'adaptive')
|
||||
return '~ 2xl:cols-5 xl:cols-4 lg:cols-3 md:cols-2 gap-5'
|
||||
if (props.gridLayout === 'twoColumns')
|
||||
return '~ cols-1 xl:cols-2 gap-4'
|
||||
return '~ cols-1 gap-4'
|
||||
})
|
||||
|
||||
const videoList = reactive<VideoItem[]>([])
|
||||
const isLoading = ref<boolean>(false)
|
||||
const containerRef = ref<HTMLElement>() as Ref<HTMLElement>
|
||||
@@ -72,10 +85,15 @@ async function getTrendingVideos() {
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- By directly using predefined unocss grid properties, it is possible to dynamically set the grid attribute -->
|
||||
<div hidden grid="~ 2xl:cols-5 xl:cols-4 lg:cols-3 md:cols-2 gap-5" />
|
||||
<div hidden grid="~ cols-1 xl:cols-2 gap-4" />
|
||||
<div hidden grid="~ cols-1 gap-4" />
|
||||
|
||||
<div
|
||||
ref="containerRef"
|
||||
m="b-0 t-0" relative w-full h-full
|
||||
grid="~ cols-1 xl:cols-2 gap-4"
|
||||
:grid="gridValue"
|
||||
>
|
||||
<VideoCard
|
||||
v-for="video in videoList"
|
||||
@@ -95,13 +113,15 @@ async function getTrendingVideos() {
|
||||
:tag="video.rcmd_reason.content"
|
||||
:cid="video.cid"
|
||||
show-preview
|
||||
horizontal
|
||||
w-full
|
||||
:horizontal="gridLayout !== 'adaptive'"
|
||||
/>
|
||||
|
||||
<!-- skeleton -->
|
||||
<template v-if="isLoading">
|
||||
<VideoCardSkeleton v-for="item in 30" :key="item" horizontal />
|
||||
<VideoCardSkeleton
|
||||
v-for="item in 30" :key="item"
|
||||
:horizontal="gridLayout !== 'adaptive'"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { GridLayout } from '~/logic'
|
||||
|
||||
export enum HomeSubPage {
|
||||
ForYou = 'ForYou',
|
||||
Following = 'Following',
|
||||
@@ -13,3 +15,9 @@ export interface RankingType {
|
||||
seasonType?: number
|
||||
type?: string
|
||||
}
|
||||
|
||||
export interface GridLayoutIcon {
|
||||
icon: string
|
||||
iconActivated: string
|
||||
value: GridLayout
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user