windows/components/LiveStreamHub.vue

402 lines
9.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
// Mock data for live streams
const featuredStreams = computed(() => [
{
id: 1,
image: "https://picsum.photos/800/400?random=1",
externalUrl: "https://twitch.tv/gamingking"
},
{
id: 2,
image: "https://picsum.photos/800/400?random=2",
externalUrl: "https://youtube.com/watch?v=music123"
},
{
id: 3,
image: "https://picsum.photos/800/400?random=3",
externalUrl: "https://twitch.tv/cookingchef"
},
{
id: 4,
image: "https://picsum.photos/800/400?random=4",
externalUrl: "https://youtube.com/watch?v=coding123"
}
]);
const popularStreamers = computed(() => [
{ id: 1, name: t('livestream.streamers.gamingKing'), avatar: "🎮", viewers: 15420, isLive: true },
{ id: 2, name: t('livestream.streamers.musicMaster'), avatar: "🎵", viewers: 8930, isLive: true },
{ id: 3, name: t('livestream.streamers.cookingChef'), avatar: "🍳", viewers: 5670, isLive: true },
{ id: 4, name: t('livestream.streamers.codeTeacher'), avatar: "💻", viewers: 12300, isLive: true },
{ id: 5, name: t('livestream.streamers.artist'), avatar: "🎨", viewers: 4200, isLive: true },
{ id: 6, name: t('livestream.streamers.trainer'), avatar: "💪", viewers: 6800, isLive: false },
{ id: 7, name: t('livestream.streamers.traveler'), avatar: "✈️", viewers: 3200, isLive: true },
{ id: 8, name: t('livestream.streamers.petLover'), avatar: "🐱", viewers: 5100, isLive: false }
]);
// Carousel state
const currentSlide = ref(0);
const carouselInterval = ref<number | null>(null);
// Auto-play carousel
const startCarousel = () => {
carouselInterval.value = window.setInterval(() => {
currentSlide.value = (currentSlide.value + 1) % featuredStreams.value.length;
}, 4000);
};
const stopCarousel = () => {
if (carouselInterval.value) {
clearInterval(carouselInterval.value);
carouselInterval.value = null;
}
};
// Handle stream click
const handleStreamClick = (stream: any) => {
if (stream.externalUrl) {
window.open(stream.externalUrl, '_blank', 'noopener,noreferrer');
}
};
// Handle streamer click
const handleStreamerClick = (streamer: any) => {
console.log(`查看主播: ${streamer.name}`);
// TODO: Open streamer profile
};
onMounted(() => {
startCarousel();
});
onUnmounted(() => {
stopCarousel();
});
</script>
<template>
<div class="livestream-hub">
<!-- Header -->
<div class="hub-header">
<h2 class="hub-title">📺 {{ t('livestream.title') }}</h2>
<div class="hub-subtitle">{{ t('livestream.subtitle') }}</div>
</div>
<!-- Home View -->
<div class="home-view">
<!-- Featured Streams Carousel -->
<div class="carousel-section">
<h3 class="section-title">🔥 {{ t('livestream.featuredStreams') }}</h3>
<div class="carousel-container" @mouseenter="stopCarousel" @mouseleave="startCarousel">
<div class="carousel-track" :style="{ transform: `translateX(-${currentSlide * 100}%)` }">
<div
v-for="stream in featuredStreams"
:key="stream.id"
class="carousel-slide"
@click="handleStreamClick(stream)"
>
<div
class="stream-image-card"
:style="{ backgroundImage: `url(${stream.image})` }"
>
</div>
</div>
</div>
<!-- Carousel Controls -->
<button
class="carousel-btn prev"
@click="currentSlide = currentSlide > 0 ? currentSlide - 1 : featuredStreams.length - 1"
>
</button>
<button
class="carousel-btn next"
@click="currentSlide = (currentSlide + 1) % featuredStreams.length"
>
</button>
<!-- Carousel Indicators -->
<div class="carousel-indicators">
<button
v-for="(stream, index) in featuredStreams"
:key="index"
class="indicator"
:class="{ active: index === currentSlide }"
@click="currentSlide = index"
></button>
</div>
</div>
</div>
<!-- Popular Streamers -->
<div class="streamers-section">
<h3 class="section-title">⭐ {{ t('livestream.popularStreamers') }}</h3>
<div class="streamers-grid">
<div
v-for="streamer in popularStreamers"
:key="streamer.id"
class="streamer-card"
@click="handleStreamerClick(streamer)"
>
<div class="streamer-avatar">{{ streamer.avatar }}</div>
<div class="streamer-info">
<div class="streamer-name">{{ streamer.name }}</div>
<div class="streamer-viewers">{{ streamer.viewers }} {{ t('livestream.viewers') }}</div>
</div>
<div class="streamer-status" :class="{ 'is-live': streamer.isLive }">
{{ streamer.isLive ? `🔴 ${t('livestream.live')}` : ` ${t('livestream.offline')}` }}
</div>
</div>
</div>
</div>
</div> <!-- End home-view -->
</div>
</template>
<style scoped>
.livestream-hub {
width: 100%;
height: 100%;
background: var(--window-background);
display: flex;
flex-direction: column;
padding: 20px;
box-sizing: border-box;
overflow-y: auto;
}
.hub-header {
text-align: center;
margin-bottom: 24px;
}
.hub-title {
font-size: 24px;
font-weight: bold;
color: var(--content-text-color);
margin: 0 0 8px 0;
}
.hub-subtitle {
font-size: 14px;
color: var(--content-text-color);
opacity: 0.7;
}
.carousel-section {
margin-bottom: 32px;
}
.section-title {
font-size: 18px;
font-weight: bold;
color: var(--content-text-color);
margin: 0 0 16px 0;
}
.carousel-container {
position: relative;
width: 100%;
height: 300px;
overflow: hidden;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}
.carousel-track {
display: flex;
width: 100%;
height: 100%;
transition: transform 0.5s ease-in-out;
}
.carousel-slide {
min-width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.stream-image-card {
width: 100%;
height: 100%;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
position: relative;
cursor: pointer;
transition: transform 0.3s ease;
}
.stream-image-card:hover {
transform: scale(1.02);
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.carousel-btn {
position: absolute;
top: 50%;
transform: translateY(-50%);
background: rgba(0, 0, 0, 0.5);
border: none;
color: white;
font-size: 24px;
width: 50px;
height: 50px;
border-radius: 50%;
cursor: pointer;
transition: all 0.3s ease;
z-index: 10;
backdrop-filter: blur(10px);
display: flex;
align-items: center;
justify-content: center;
}
.carousel-btn:hover {
background: rgba(0, 0, 0, 0.7);
transform: translateY(-50%) scale(1.1);
}
.carousel-btn.prev {
left: 16px;
}
.carousel-btn.next {
right: 16px;
}
.carousel-indicators {
position: absolute;
bottom: 16px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 8px;
z-index: 10;
}
.indicator {
width: 12px;
height: 12px;
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.6);
background: rgba(255, 255, 255, 0.3);
cursor: pointer;
transition: all 0.3s ease;
flex-shrink: 0;
aspect-ratio: 1;
min-width: 12px;
min-height: 12px;
}
.indicator.active {
background: white;
border-color: white;
transform: scale(1.2);
}
.streamers-section {
flex: 1;
}
.streamers-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
}
.streamer-card {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
background: var(--window-background);
border: 1px solid var(--window-border-color);
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
}
.streamer-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
border-color: var(--window-border-color-focused);
}
.streamer-avatar {
font-size: 32px;
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
background: var(--title-bar-background);
border-radius: 50%;
flex-shrink: 0;
}
.streamer-info {
flex: 1;
}
.streamer-name {
font-weight: bold;
color: var(--content-text-color);
margin-bottom: 4px;
}
.streamer-viewers {
font-size: 12px;
color: var(--content-text-color);
opacity: 0.7;
}
.streamer-status {
font-size: 16px;
flex-shrink: 0;
}
.streamer-status.is-live {
animation: pulse 2s infinite;
}
/* Light theme adjustments */
.theme-light .carousel-btn {
background: rgba(255, 255, 255, 0.8);
color: #333;
}
.theme-light .carousel-btn:hover {
background: rgba(255, 255, 255, 0.9);
}
.theme-light .indicator {
border-color: rgba(0, 0, 0, 0.6);
background: rgba(0, 0, 0, 0.3);
}
.theme-light .indicator.active {
background: #333;
border-color: #333;
}
</style>