Add games popup (#5055)

This commit is contained in:
the1812 2025-04-03 08:56:05 +08:00
parent 1d1246eb4a
commit 81ef584251
6 changed files with 219 additions and 11 deletions

View File

@ -4,7 +4,8 @@ import { ranking } from './ranking/ranking'
import { userInfo } from './user-info/user-info'
import { logo } from './logo/logo'
import { home } from './home/home'
import { gamesIframe, livesIframe, mangaIframe } from './iframe/iframe'
import { livesIframe, mangaIframe } from './iframe/iframe'
import { games } from './games/games'
import { blanks } from './flexible-blank/flexible-blank'
import { bangumi, music, shop, match, creations } from './simple-links/simple-links'
import { upload } from './upload/upload'
@ -23,7 +24,7 @@ export const getBuiltInItems = (): CustomNavbarItemInit[] => [
bangumi,
ranking,
music,
gamesIframe,
games,
livesIframe,
shop,
match,

View File

@ -0,0 +1,170 @@
<script setup lang="ts">
import { ref, defineExpose, defineProps } from 'vue'
import { VLoading } from '@/ui'
import type { GameInfo } from './types'
import { bilibiliApi, getJson } from '@/core/ajax'
import { usePopper } from '../mixins'
import { CustomNavbarItem } from '../custom-navbar-item'
const props = defineProps<{
item: CustomNavbarItem
container: HTMLElement
}>()
const popper = usePopper(props)
const gameInfo = ref<GameInfo | undefined>(undefined)
const loading = ref(true)
const loadGameInfo = async () => {
if (gameInfo.value !== undefined) {
return
}
const response = await bilibiliApi<GameInfo>(
getJson('https://le3-api.game.bilibili.com/pc/game/nav/info'),
)
gameInfo.value = response
loading.value = false
}
defineExpose({
popupShow() {
popper.popupShow()
loadGameInfo()
},
})
</script>
<template>
<div class="custom-navbar-games-popup">
<div v-if="loading" class="loading-container">
<VLoading />
</div>
<div v-if="gameInfo" class="custom-navbar-games-panel">
<div class="custom-navbar-games-left-panel">
<a class="banner-game-card" :href="gameInfo.banner.href" :title="gameInfo.banner.title">
<img :src="gameInfo.banner.poster" :alt="gameInfo.banner.title" />
<div class="banner-game-card-title">
{{ gameInfo.banner.title }}
</div>
</a>
<div class="panel-game-cards">
<a
v-for="card of gameInfo.panel"
:key="card.href"
class="panel-game-card"
:href="card.href"
:title="card.title"
>
<img :src="card.poster" :alt="card.title" />
<div class="panel-game-card-title">
{{ card.title }}
</div>
</a>
</div>
</div>
<div class="custom-navbar-games-panel-separator"></div>
<div class="custom-navbar-games-right-panel">
<div class="list-game-cards-title">新游预告</div>
<div class="list-game-cards">
<a
v-for="card of gameInfo.list.slice(0, 9)"
:key="card.href"
class="list-game-card"
:href="card.href"
:title="card.title"
>
{{ card.title }}
</a>
</div>
</div>
</div>
</div>
</template>
<style lang="scss">
@import 'common';
.custom-navbar-games-popup {
width: 420px;
font-size: 12px;
$gap: 12px;
@include v-stretch();
.loading-container {
flex-grow: 1;
@include v-center();
justify-content: center;
height: 240px;
font-size: 14px;
}
.custom-navbar-games-panel {
@include h-stretch($gap);
padding: $gap;
}
.custom-navbar-games-left-panel {
flex-basis: 60%;
min-width: 0;
@include v-stretch($gap);
.banner-game-card {
position: relative;
overflow: hidden;
@include v-stretch();
@include round-corner();
img {
transition: transform 0.2s ease-out;
}
&:hover img {
transform: scale(1.05);
}
&-title {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
font-size: 12px;
color: #fff;
padding: 8px;
background-image: linear-gradient(to top, #0008, transparent);
}
}
.panel-game-cards {
@include h-stretch($gap);
.panel-game-card {
@include v-stretch(6px);
min-width: 0;
flex: 1;
transition: color 0.2s ease-out;
&:hover {
color: var(--theme-color) !important;
}
&-title {
@include max-line(2);
text-align: center;
}
}
}
}
.custom-navbar-games-right-panel {
min-width: 0;
flex: 1;
@include v-stretch($gap);
padding: 6px 4px;
.list-game-cards-title {
font-size: large;
@include semi-bold();
}
.list-game-cards {
@include v-stretch(8px);
.list-game-card {
@include single-line();
opacity: 0.6;
transition: all 0.2s ease-out;
&:hover {
color: var(--theme-color) !important;
opacity: 1;
}
}
}
}
.custom-navbar-games-panel-separator {
width: 1px;
background-color: #8882;
}
}
</style>

View File

@ -0,0 +1,14 @@
import { CustomNavbarItemInit } from '../custom-navbar-item'
export const games: CustomNavbarItemInit = {
name: 'games',
displayName: '游戏中心',
content: '游戏中心',
touch: true,
href: 'https://game.bilibili.com/',
boundingWidth: 420,
noPopupPadding: true,
popupContent: () => import('./GamesPopup.vue').then(m => m.default),
}

View File

@ -0,0 +1,11 @@
export interface GameListItem {
href: string
title: string
poster: string
}
export interface GameInfo {
panel: GameListItem[]
list: GameListItem[]
banner: GameListItem
}

View File

@ -21,15 +21,6 @@ const getIframeItem = (config: NavbarIframeConfig): CustomNavbarItemInit & Navba
noPopupPadding: true,
transparentPopup: true,
})
export const gamesIframe = getIframeItem({
src: 'https://www.bilibili.com/page-proxy/game-nav.html',
href: 'https://game.bilibili.com/',
width: 680,
height: 260,
lazy: true,
displayName: '游戏中心',
iframeName: 'games',
})
export const livesIframe = getIframeItem({
src: 'https://live.bilibili.com/blackboard/dropdown-menu.html',
href: 'https://live.bilibili.com',

View File

@ -1,3 +1,4 @@
import { onMounted } from 'vue'
import { CustomNavbarItem } from './custom-navbar-item'
export const popperMixin = Vue.extend({
@ -25,3 +26,23 @@ export const popperMixin = Vue.extend({
},
},
})
export interface UsePopperProps {
item: CustomNavbarItem
container: HTMLElement
}
export const usePopper = (props: UsePopperProps) => {
onMounted(() => {
const navBarItem = props.item
const containerElement = props.container
if (containerElement) {
navBarItem?.usePopper(containerElement, containerElement.children[0] as HTMLElement)
}
})
return {
popupShow() {
const navBarItem = props.item
navBarItem?.popper?.update()
},
}
}