Add minimal home feeds

This commit is contained in:
the1812 2022-06-25 15:59:40 +08:00
parent 4bcb6d6a39
commit 2a82a24720
10 changed files with 195 additions and 96 deletions

View File

@ -109,7 +109,7 @@ import {
} from '@/ui'
import { addComponentListener } from '@/core/settings'
import { enableHorizontalScroll } from '@/core/horizontal-scroll'
import { cssVariableMixin, requestMixin } from './mixin'
import { cssVariableMixin, requestMixin } from '../../../../mixin'
import { rankListCssVars } from './rank-list'
import { setupScrollMask, cleanUpScrollMask } from '../../../scroll-mask'

View File

@ -143,7 +143,7 @@ import {
VEmpty,
VButton,
} from '@/ui'
import { requestMixin, cssVariableMixin } from './mixin'
import { requestMixin, cssVariableMixin } from '../../../../mixin'
import { rankListCssVars } from './rank-list'
export default Vue.extend({

View File

@ -130,7 +130,7 @@ import { VideoCard } from '@/components/feeds/video-card'
import { getWatchlaterList, toggleWatchlater, watchlaterList } from '@/components/video/watchlater'
import { formatDuration } from '@/core/utils/formatters'
import { DpiImage, VButton, VIcon, VLoading, VEmpty } from '@/ui'
import { cssVariableMixin, requestMixin } from './mixin'
import { cssVariableMixin, requestMixin } from '../../../../mixin'
export default Vue.extend({
components: {

View File

@ -1,64 +1,72 @@
<template>
<HomeRedesignBase>
<div class="minimal-home">
<div class="minimal-home-tabs">
<div class="default-tabs">
<MinimalHomeFeeds @tab-change="onTabChange" />
<MinimalHomeTrending @tab-change="onTabChange" />
</div>
</div>
<TabControl class="minimal-home-tabs" :tabs="tabs" />
</div>
</HomeRedesignBase>
</template>
<script lang="ts">
import { addComponentListener } from '@/core/settings'
import { TabControl } from '@/ui'
import { TabMappings } from '@/ui/tab-mapping'
import HomeRedesignBase from '../HomeRedesignBase.vue'
import { minimalHomeOptions } from './options'
import MinimalHomeFeeds from './tabs/MinimalHomeFeeds.vue'
import MinimalHomeTrending from './tabs/MinimalHomeTrending.vue'
import { MinimalHomeTabOption } from './types'
const tabs = [
MinimalHomeFeeds,
MinimalHomeTrending,
const tabs: TabMappings = [
{
name: MinimalHomeTabOption.Feeds,
displayName: '动态',
component: () => import('./tabs/MinimalHomeFeeds.vue').then(m => m.default),
activeLink: 'https://t.bilibili.com/?tab=video',
},
{
name: MinimalHomeTabOption.Trending,
displayName: minimalHomeOptions.personalized ? '推荐' : '热门',
component: () => import('./tabs/MinimalHomeTrending.vue').then(m => m.default),
activeLink: 'https://www.bilibili.com/v/popular/all',
},
]
export default Vue.extend({
components: {
HomeRedesignBase,
MinimalHomeFeeds,
MinimalHomeTrending,
TabControl,
},
data() {
return {
tabs,
selectedTab: minimalHomeOptions.defaultTab,
}
},
computed: {
},
methods: {
onTabChange(newTab: MinimalHomeTabOption) {
this.selectedTab = newTab
},
mounted() {
const columnCountKey = '--minimal-home-column-count-override'
addComponentListener('minimalHome.columnCount', (count: number) => {
if (count > 0) {
(this.$el as HTMLElement).style.setProperty(columnCountKey, count.toString())
} else {
(this.$el as HTMLElement).style.removeProperty(columnCountKey)
}
}, true)
},
})
</script>
<style lang="scss">
@import 'common';
@import 'tabs';
/* TODO:
- 默认列数自适应
- 720P 一列
- 1080P 两列
- 21:9 四列
*/
.minimal-home {
&-tabs {
@include tabs-style();
.default-tabs {
padding: 4px 8px;
}
--minimal-home-auto-card-columns: 1;
--card-width: 600px;
--card-height: 122px;
@media screen and (min-width: 1080px) {
--minimal-home-auto-card-column: 2;
}
@media screen and (min-width: 2520px) {
--minimal-home-auto-card-column: 3;
}
--minimal-home-card-column: var(
--minimal-home-column-count-override,
var(--minimal-home-auto-card-column)
);
padding: 24px 32px;
}
</style>

View File

@ -1,14 +1,80 @@
<template>
<div class="default-tab" data-name="feeds" @click="onTabChange">动态</div>
<div class="minimal-home-feeds" :class="{ loading, loaded, error }">
<div class="minimal-home-feeds-cards">
<VideoCard
v-for="c of cards"
:key="c.id"
:data="c"
/>
</div>
<VEmpty v-if="loaded && cards.length === 0" />
<ScrollTrigger @trigger="loadCards" />
</div>
</template>
<script lang="ts">
import { MinimalHomeTabOption } from '../types'
import { getVideoFeeds } from '@/components/feeds/api'
import { VideoCard } from '@/components/feeds/video-card'
import VideoCardComponent from '@/components/feeds/VideoCard.vue'
import { logError } from '@/core/utils/log'
import { ascendingStringSort } from '@/core/utils/sort'
import {
VEmpty,
ScrollTrigger,
} from '@/ui'
import { cssVariableMixin } from '../../mixin'
export default Vue.extend({
components: { ScrollTrigger, VEmpty, VideoCard: VideoCardComponent },
mixins: [cssVariableMixin({
})],
data() {
return {
loading: true,
cards: [],
error: false,
}
},
computed: {
loaded() {
return !this.loading && !this.error
},
lastID() {
if (!this.cards.length) {
return null
}
const cards: VideoCard[] = [...this.cards]
return cards.sort(ascendingStringSort(c => c.id))[0].id
},
},
methods: {
onTabChange() {
this.$emit('tab-change', MinimalHomeTabOption.Feeds)
async loadCards() {
try {
this.error = false
this.loading = true
this.cards = lodash.uniqBy([...this.cards, ...await getVideoFeeds('video', this.lastID)], it => it.id)
} catch (error) {
logError(error)
this.error = true
} finally {
this.loading = false
}
},
},
})
</script>
<style lang="scss">
.minimal-home-feeds {
&-cards {
display: grid;
grid-template-columns: repeat(var(--minimal-home-card-column), var(--card-width));
gap: 12px;
padding: 0 8px;
margin-bottom: 16px;
.video-card * {
transition: .2s ease-out;
}
}
}
</style>

View File

@ -1,20 +1,13 @@
<template>
<div class="default-tab" data-name="trending">{{ title }}</div>
<div></div>
</template>
<script lang="ts">
import { minimalHomeOptions } from '../options'
import { MinimalHomeTabOption } from '../types'
export default Vue.extend({
computed: {
title() {
return minimalHomeOptions.personalized ? '推荐' : '热门'
},
},
methods: {
onTabChange() {
this.$emit('tab-change', MinimalHomeTabOption.Trending)
},
},
})
</script>

View File

@ -2,11 +2,19 @@
<a
class="video-card"
target="_blank"
:href="epID ? ('https://www.bilibili.com/bangumi/play/ep' + epID) : ('https://www.bilibili.com/video/' + bvid)"
:href="
epID
? 'https://www.bilibili.com/bangumi/play/ep' + epID
: 'https://www.bilibili.com/video/' + bvid
"
:class="{ vertical: orientation === 'vertical', 'no-stats': !showStats }"
>
<div class="cover-container">
<DpiImage class="cover" :src="coverUrl" :size="{ height: 120, width: 200 }"></DpiImage>
<DpiImage
class="cover"
:src="coverUrl"
:size="{ height: 120, width: 196 }"
></DpiImage>
<div v-if="isNew" class="new">NEW</div>
<template v-if="pubTime && pubTimeText">
<div class="publish-time-summary">
@ -32,12 +40,18 @@
<h1 class="title" :title="title">{{ title }}</h1>
<div v-if="topics && topics.length" class="topics">
<a
v-for="topic of topics.slice(0,3)"
v-for="topic of topics.slice(0, 3)"
:key="topic.id"
:title="topic.name"
class="topic"
target="_blank"
:href="'https://t.bilibili.com/topic/name/' + topic.name + '/feed'"
>#{{ topic.name }}#</a>
>
<VIcon icon="mdi-tag-outline" :size="14" />
<div class="topic-name">
{{ topic.name }}
</div>
</a>
</div>
<p v-else class="description" :title="description">{{ description }}</p>
<a
@ -45,7 +59,7 @@
class="up"
:class="{ 'no-face': !upFaceUrl }"
target="_blank"
:href="upID ? ('https://space.bilibili.com/' + upID) : null"
:href="upID ? 'https://space.bilibili.com/' + upID : null"
>
<DpiImage v-if="upFaceUrl" class="face" :src="upFaceUrl" :size="24" />
<VIcon v-else icon="up" />
@ -60,15 +74,18 @@
:class="{ 'no-face': !up.faceUrl }"
target="_blank"
:title="up.name"
:href="up.id ? ('https://space.bilibili.com/' + up.id) : null"
:href="up.id ? 'https://space.bilibili.com/' + up.id : null"
>
<DpiImage v-if="up.faceUrl" class="face" :src="up.faceUrl" :size="24" />
<DpiImage
v-if="up.faceUrl"
class="face"
:src="up.faceUrl"
:size="24"
/>
<VIcon v-else icon="up" />
</a>
</div>
<div class="cooperation-note">
联合投稿
</div>
<div class="cooperation-note">联合投稿</div>
</div>
<div v-if="showStats" class="stats">
<template v-if="vertical">
@ -198,11 +215,11 @@ export default {
</script>
<style lang="scss" scoped>
@import "common";
@import 'common';
.video-card {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-columns: 196px 1fr;
grid-template-rows: 1fr 1fr 1fr;
grid-template-areas:
'cover title'
@ -245,6 +262,8 @@ export default {
}
.cover-container {
border-radius: $radius $radius 0 0;
width: calc(var(--card-width) - 2px);
height: calc(var(--card-width) / 20 * 12.5);
}
.title {
display: -webkit-box;
@ -255,6 +274,7 @@ export default {
white-space: normal;
line-height: 1.5;
margin: 4px 0;
padding: 0 10px;
font-size: 14px;
}
.up {
@ -315,8 +335,8 @@ export default {
grid-area: cover;
border-radius: $radius 0 0 $radius;
position: relative;
width: calc(var(--card-width) - 2px);
height: calc(var(--card-width) / 20 * 12.5);
width: calc(var(--card-height) / 12.5 * 20);
height: calc(var(--card-height) - 2px);
overflow: hidden;
.cover {
transition: 0.1s cubic-bezier(0.39, 0.58, 0.57, 1);
@ -377,11 +397,11 @@ export default {
}
.title {
grid-area: title;
font-size: 16px;
font-size: 15px;
// font-weight: bold;
@include semi-bold();
color: inherit;
padding: 0 10px;
padding: 4px 12px 0 12px;
white-space: nowrap;
overflow: hidden;
justify-self: stretch;
@ -391,23 +411,25 @@ export default {
}
}
.topics {
@include h-center();
grid-area: description;
display: flex;
align-items: center;
margin-left: 12px;
.topic {
@include h-center(4px);
color: inherit;
padding: 4px 8px;
background-color: #8882;
border: 1px solid #8882;
margin-right: 8px;
border-radius: 14px;
white-space: nowrap;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
opacity: 0.75;
.topic-name {
max-width: 120px;
@include single-line();
}
&:hover {
background-color: #8884;
background-color: #8882;
color: var(--theme-color);
opacity: 1;
}
}
}
@ -518,7 +540,7 @@ export default {
}
}
&-note {
opacity: .5;
opacity: 0.5;
}
}
.stats {

View File

@ -5,7 +5,7 @@ import { watchlaterList } from '@/components/video/watchlater'
import { getData, registerData } from '@/plugins/data'
import { descendingStringSort } from '@/core/utils/sort'
import { VideoCard } from '../video-card'
import { FeedsCard, FeedsCardType } from './types'
import { FeedsCard, FeedsCardType, feedsCardTypes } from './types'
export * from './types'
export * from './manager'
@ -59,18 +59,42 @@ export const withContentFilter = <Args extends any[], Item> (
func: (...args: Args) => Promise<Item[]>,
) => (...args: Args) => func(...args).then(items => applyContentFilter(items))
/**
* API
* @param type , ID列表返回最新动态
* @param afterID ID之前的动态历史,
*/
export const getFeedsUrl = (type: FeedsCardType | string, afterID?: string | number) => {
if (typeof type === 'string') {
return `https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_new?uid=${getUID()}&type_list=${type}`
}
const id = type.id.toString()
let api = `https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_new?uid=${getUID()}&type_list=${id}`
if (afterID) {
api = `https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_history?uid=${getUID()}&offset_dynamic_id=${afterID}&type=${id}`
}
return api
}
/**
*
* @param type , ID列表返回最新动态
* @param afterID ID之前的动态历史,
*/
export const getFeeds = async (type: FeedsCardType | string, afterID?: string | number) => (
getJsonWithCredentials(getFeedsUrl(type, afterID))
)
/**
*
* @param type (video) (bangumi) ,
* @param afterID ID之前的动态历史,
*/
export const getVideoFeeds = withContentFilter(
async (type: 'video' | 'bangumi' = 'video'): Promise<VideoCard[]> => {
async (type: 'video' | 'bangumi' = 'video', afterID?: string | number): Promise<VideoCard[]> => {
if (!getUID()) {
return []
}
const json = await getJsonWithCredentials(
`https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_new?uid=${getUID()}&type_list=${type === 'video' ? 8 : 512}`,
)
const json = await getJsonWithCredentials(getFeedsUrl(type === 'video' ? feedsCardTypes.video : feedsCardTypes.bangumi, afterID))
if (json.code !== 0) {
throw new Error(json.message)
}
@ -138,23 +162,6 @@ export const getVideoFeeds = withContentFilter(
},
)
// let mockupCalled = false
/**
*
* @param type , ID列表返回最新动态
* @param afterID ID之前的动态历史,
*/
export const getFeeds = async (type: FeedsCardType | string, afterID?: string | number) => {
if (typeof type === 'string') {
return getJsonWithCredentials(`https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_new?uid=${getUID()}&type_list=${type}`)
}
const id = type.id.toString()
let api = `https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_new?uid=${getUID()}&type_list=${id}`
if (afterID) {
api = `https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_history?uid=${getUID()}&offset_dynamic_id=${afterID}&type=${id}`
}
return getJsonWithCredentials(api)
}
/**
*
* @param card

View File

@ -117,6 +117,9 @@ export default Vue.extend({
.header-item {
flex: 1;
margin: 0 8px;
&:empty {
display: none;
}
}
.be-more-link {
.be-button {