876 lines
24 KiB
Vue
876 lines
24 KiB
Vue
|
<script setup lang="ts">
|
|||
|
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
|||
|
import { useI18n } from 'vue-i18n';
|
|||
|
import StreamerFlipCard from './StreamerFlipCard.vue';
|
|||
|
|
|||
|
const { t } = useI18n();
|
|||
|
|
|||
|
// Mock data for live streams
|
|||
|
const featuredStreams = computed(() => [
|
|||
|
{
|
|||
|
id: 1,
|
|||
|
image: "https://imgproxy.goplayone.com/1/auto/768/0/sm/0/aHR0cHM6Ly9wbGF5b25lLWFzc2V0cy5nb3BsYXlvbmUuY29tL3BsYXlvbmUvYmFubmVyLzNlYTNkMzI0ZjJhMmZhNDQ5MDAyZWQyNDE0ZDNhZDhlLnBuZw==",
|
|||
|
externalUrl: "https://twitch.tv/gamingking"
|
|||
|
},
|
|||
|
{
|
|||
|
id: 2,
|
|||
|
image: "https://imgproxy.goplayone.com/1/auto/768/0/sm/0/aHR0cHM6Ly9wbGF5b25lLWFzc2V0cy5nb3BsYXlvbmUuY29tL3BsYXlvbmUvYmFubmVyLzE5OGI4OGZhZmQ3ZDM2NjEyZmE0YTBmZDQ0NzViMmVjLnBuZw==",
|
|||
|
externalUrl: "https://youtube.com/watch?v=music123"
|
|||
|
},
|
|||
|
{
|
|||
|
id: 3,
|
|||
|
image: "https://imgproxy.goplayone.com/1/auto/768/0/sm/0/aHR0cHM6Ly9wbGF5b25lLWFzc2V0cy5nb3BsYXlvbmUuY29tL3BsYXlvbmUvYmFubmVyLzVjMmM1NzRhNTY0ZDNhMmU4ODM5OTlhZTQ3NDk1NTQ5LnBuZw==",
|
|||
|
externalUrl: "https://twitch.tv/cookingchef"
|
|||
|
},
|
|||
|
{
|
|||
|
id: 4,
|
|||
|
image: "https://imgproxy.goplayone.com/1/auto/768/0/sm/0/aHR0cHM6Ly9wbGF5b25lLWFzc2V0cy5nb3BsYXlvbmUuY29tL3BsYXlvbmUvYmFubmVyL2JjNGI4NmJlNzM5OWJlNGJmNTY2MTk0YjZmOWZkZDYwLnBuZw==",
|
|||
|
externalUrl: "https://youtube.com/watch?v=coding123"
|
|||
|
}
|
|||
|
]);
|
|||
|
|
|||
|
const popularStreamers = computed(() => [
|
|||
|
{
|
|||
|
id: 1,
|
|||
|
name: "音樂小天使",
|
|||
|
photo: "https://playone-assets.goplayone.com/playone/user/play/avatar/ea249b64-a3d5-4ff1-bdc5-2eb0d0f956ea",
|
|||
|
description: "音樂達人♫",
|
|||
|
rank: "黃金",
|
|||
|
fans: 1560,
|
|||
|
orders: 112,
|
|||
|
badges: ["rank-gold", "pro-music", "feature-verified"],
|
|||
|
gender: "female" as const,
|
|||
|
birthday: "1998-03-15",
|
|||
|
greeting: "你好~我是音樂小天使~音樂達人♫",
|
|||
|
status: "專業音樂陪陪!",
|
|||
|
availability: "隨時可約 聲音甜美",
|
|||
|
personality: "溫柔體貼 歌聲動人",
|
|||
|
promise: "用音樂治癒你的心靈~快來聽我唱歌吧💕"
|
|||
|
},
|
|||
|
{
|
|||
|
id: 2,
|
|||
|
name: "電競女武神",
|
|||
|
photo: "https://imgproxy.goplayone.com/1/auto/244/244/sm/0/aHR0cHM6Ly9wbGF5b25lLWFzc2V0cy5nb3BsYXlvbmUuY29tL3BsYXlvbmUvdXNlci9wbGF5L2F2YXRhci83NDk5ODA2OS04YTU4LTQ5YWQtOTc1YS1jNTczZmY0YjNjNTY=",
|
|||
|
description: "FPS女王",
|
|||
|
rank: "大師",
|
|||
|
fans: 2800,
|
|||
|
orders: 198,
|
|||
|
badges: ["rank-master", "pro-gaming", "feature-live"],
|
|||
|
gender: "female" as const,
|
|||
|
birthday: "1995-07-22",
|
|||
|
greeting: "Yo~我是電競女武神~FPS女王🔫",
|
|||
|
status: "前職業選手 現役陪陪!",
|
|||
|
availability: "晚上8-12點 週末全天",
|
|||
|
personality: "冷靜狙擊 一槍一個",
|
|||
|
promise: "帶你體驗職業級操作!從菜鳥到高手 包教包會🎯"
|
|||
|
},
|
|||
|
{
|
|||
|
id: 3,
|
|||
|
name: "二次元萌妹",
|
|||
|
photo: "https://playone-assets.goplayone.com/playone/user/play/avatar/a4b858a7-a1f3-4543-8d18-ae94e8db573b",
|
|||
|
description: "動漫專家",
|
|||
|
rank: "黃金",
|
|||
|
fans: 1200,
|
|||
|
orders: 89,
|
|||
|
badges: ["rank-gold", "special-anime", "feature-new"],
|
|||
|
gender: "female" as const,
|
|||
|
birthday: "2000-11-08",
|
|||
|
greeting: "こんにちは~我是二次元萌妹~動漫專家🌸",
|
|||
|
status: "動漫系大學生 兼職陪陪!",
|
|||
|
availability: "平日晚上 週末下午",
|
|||
|
personality: "超愛動漫 聲音超萌",
|
|||
|
promise: "一起討論最新番劇!陪你刷副本 收集老婆💖"
|
|||
|
},
|
|||
|
{
|
|||
|
id: 4,
|
|||
|
name: "策略大師",
|
|||
|
photo: "https://imgproxy.goplayone.com/1/auto/244/244/sm/0/aHR0cHM6Ly9wbGF5b25lLWFzc2V0cy5nb3BsYXlvbmUuY29tL3BsYXlvbmUvdXNlci9wbGF5L2F2YXRhci8zZjMzMTZlYS04YzRkLTQ0Y2UtOTc5Mi0zMTk2ZmUxZmJhOGU=",
|
|||
|
description: "戰術專家",
|
|||
|
rank: "鑽石",
|
|||
|
fans: 4200,
|
|||
|
orders: 312,
|
|||
|
badges: ["rank-diamond", "achievement-expert", "feature-vip"],
|
|||
|
gender: "male" as const,
|
|||
|
birthday: "1992-05-18",
|
|||
|
greeting: "你好~我是策略大師~戰術專家🧠",
|
|||
|
status: "前職業教練 現專職陪陪!",
|
|||
|
availability: "週一到週五 下午2-8點",
|
|||
|
personality: "理性分析 耐心指導",
|
|||
|
promise: "從戰術思維到操作細節 全面提升你的遊戲智商!📊"
|
|||
|
},
|
|||
|
{
|
|||
|
id: 5,
|
|||
|
name: "派對女王",
|
|||
|
photo: "https://imgproxy.goplayone.com/1/auto/244/244/sm/0/aHR0cHM6Ly9wbGF5b25lLWFzc2V0cy5nb3BsYXlvbmUuY29tL3BsYXlvbmUvdXNlci9wbGF5L2F2YXRhci80YmUzOWUxZi04YzJhLTRiMmItYjk0NS1lYjAyMWEzNjc5ZGM=",
|
|||
|
description: "社交達人",
|
|||
|
rank: "鑽石",
|
|||
|
fans: 2100,
|
|||
|
orders: 167,
|
|||
|
badges: ["rank-diamond", "special-party", "feature-popular"],
|
|||
|
gender: "female" as const,
|
|||
|
birthday: "1996-09-12",
|
|||
|
greeting: "Hey~我是派對女王~社交達人🎉",
|
|||
|
status: "全職陪陪 專攻社交遊戲!",
|
|||
|
availability: "24小時待命 隨時開趴",
|
|||
|
personality: "超會帶氣氛 人緣超好",
|
|||
|
promise: "讓你的遊戲時光充滿歡笑!組隊開黑 一起嗨翻天🎊"
|
|||
|
},
|
|||
|
{
|
|||
|
id: 6,
|
|||
|
name: "生存專家",
|
|||
|
photo: "https://imgproxy.goplayone.com/1/auto/420/420/sm/0/aHR0cHM6Ly9wbGF5b25lLWFzc2V0cy5nb3BsYXlvbmUuY29tL3BsYXlvbmUvdXNlci9wbGF5L2F2YXRhci9hYTFhNzdjMy05YTI2LTRmNTctOTlkMC02NTA2M2IwMDgyYTY=",
|
|||
|
description: "荒野求生",
|
|||
|
rank: "大師",
|
|||
|
fans: 1800,
|
|||
|
orders: 134,
|
|||
|
badges: ["rank-master", "special-survival", "achievement-mentor"],
|
|||
|
gender: "male" as const,
|
|||
|
birthday: "1994-12-03",
|
|||
|
greeting: "Hello~我是生存專家~荒野求生🏕️",
|
|||
|
status: "建築系學生 兼職陪陪!",
|
|||
|
availability: "晚上7點後 週末全天",
|
|||
|
personality: "創意無限 耐心建造",
|
|||
|
promise: "帶你建造夢想家園!從零開始 打造專屬世界🏗️"
|
|||
|
}
|
|||
|
]);
|
|||
|
|
|||
|
// Mock data for recommended services
|
|||
|
const recommendedServices = computed(() => [
|
|||
|
{
|
|||
|
id: 1,
|
|||
|
name: "1v1 聊天",
|
|||
|
nameEn: "1v1 Chat",
|
|||
|
image: "https://imgproxy.goplayone.com/1/auto/244/94/sm/0/aHR0cHM6Ly9wbGF5b25lLWFzc2V0cy5nb3BsYXlvbmUuY29tL3BsYXlvbmUvc2tpbGwvYzQ4YzUxYjE1YmUyZTFjMDcyOTk1ZGJhZGE0MmExY2EucG5n",
|
|||
|
description: "一對一聊天服務",
|
|||
|
category: "chat"
|
|||
|
},
|
|||
|
{
|
|||
|
id: 2,
|
|||
|
name: "唱歌",
|
|||
|
nameEn: "Singing",
|
|||
|
image: "http://imgproxy.goplayone.com/1/auto/244/94/sm/0/aHR0cHM6Ly9wbGF5b25lLWFzc2V0cy5nb3BsYXlvbmUuY29tL3BsYXlvbmUvc2tpbGwvYzY1ZTJhMjAzYzRlY2U2NzhkNjkxNGE3YTBhMmQ0ODMucG5n",
|
|||
|
description: "音樂歌唱服務",
|
|||
|
category: "music"
|
|||
|
},
|
|||
|
{
|
|||
|
id: 3,
|
|||
|
name: "STEAM",
|
|||
|
nameEn: "STEAM",
|
|||
|
image: "https://imgproxy.goplayone.com/1/auto/244/94/sm/0/aHR0cHM6Ly9wbGF5b25lLWFzc2V0cy5nb3BsYXlvbmUuY29tL3BsYXlvbmUvc2tpbGwvM2RmOGQ2NzdiYzZmMGIzMzIzZmQ1MGNhYWFhZDUyMjUucG5n",
|
|||
|
description: "Steam 遊戲平台",
|
|||
|
category: "gaming"
|
|||
|
},
|
|||
|
{
|
|||
|
id: 4,
|
|||
|
name: "英雄聯盟",
|
|||
|
nameEn: "League of Legends",
|
|||
|
image: "https://imgproxy.goplayone.com/1/auto/244/94/sm/0/aHR0cHM6Ly9wbGF5b25lLWFzc2V0cy5nb3BsYXlvbmUuY29tL3BsYXlvbmUvc2tpbGwvMTljZmUxYWYwMGYyN2M4YWY1ZTYzOGFkNjM0ZDNkMDYucG5n",
|
|||
|
description: "英雄聯盟遊戲",
|
|||
|
category: "gaming"
|
|||
|
},
|
|||
|
{
|
|||
|
id: 5,
|
|||
|
name: "王者榮耀",
|
|||
|
nameEn: "Honor of Kings",
|
|||
|
image: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjEyMCIgdmlld0JveD0iMCAwIDIwMCAxMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxkZWZzPgo8bGluZWFyR3JhZGllbnQgaWQ9ImtpbmdHcmFkaWVudCIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMTAwJSI+CjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiNGRkQ3MDAiLz4KPHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSIjRkZBNTAwIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPHJlY3Qgd2lkdGg9IjIwMCIgaGVpZ2h0PSIxMjAiIGZpbGw9InVybCgja2luZ0dyYWRpZW50KSIvPgo8dGV4dCB4PSIxMDAiIHk9IjYwIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTgiIGZvbnQtd2VpZ2h0PSJib2xkIiBmaWxsPSJ3aGl0ZSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+546L5a2Q5rW35rKzPC90ZXh0Pgo8c3ZnIHg9IjgwIiB5PSIzMCIgd2lkdGg9IjQwIiBoZWlnaHQ9IjQwIiBmaWxsPSIjRkZGRkZGIj4KPGNpcmNsZSBjeD0iMjAiIGN5PSIyMCIgcj0iMTgiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSIyIi8+CjxwYXRoIGQ9Ik0xMCAxMCBMMzAgMzAgTTMwIDEwIEwxMCAzMCIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjIiLz4KPC9zdmc+Cjwvc3ZnPgo=",
|
|||
|
description: "王者榮耀手遊",
|
|||
|
category: "gaming"
|
|||
|
},
|
|||
|
{
|
|||
|
id: 6,
|
|||
|
name: "原神",
|
|||
|
nameEn: "Genshin Impact",
|
|||
|
image: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjEyMCIgdmlld0JveD0iMCAwIDIwMCAxMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxkZWZzPgo8bGluZWFyR3JhZGllbnQgaWQ9ImdlbnNoaW5HcmFkaWVudCIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMTAwJSI+CjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiM2NkMzRkYiLz4KPHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSIjMDA2NkZGIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPHJlY3Qgd2lkdGg9IjIwMCIgaGVpZ2h0PSIxMjAiIGZpbGw9InVybCgjZ2Vuc2hpbkdyYWRpZW50KSIvPgo8dGV4dCB4PSIxMDAiIHk9IjYwIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMjQiIGZvbnQtd2VpZ2h0PSJib2xkIiBmaWxsPSJ3aGl0ZSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+5Y6f5YibPC90ZXh0Pgo8c3ZnIHg9IjgwIiB5PSIzMCIgd2lkdGg9IjQwIiBoZWlnaHQ9IjQwIiBmaWxsPSIjRkZGRkZGIj4KPGNpcmNsZSBjeD0iMjAiIGN5PSIyMCIgcj0iMTgiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSIyIi8+CjxwYXRoIGQ9Ik0xMCAxMCBMMzAgMzAgTTMwIDEwIEwxMCAzMCIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjIiLz4KPC9zdmc+Cjwvc3ZnPgo=",
|
|||
|
description: "原神開放世界遊戲",
|
|||
|
category: "gaming"
|
|||
|
},
|
|||
|
{
|
|||
|
id: 7,
|
|||
|
name: "直播",
|
|||
|
nameEn: "Live Streaming",
|
|||
|
image: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjEyMCIgdmlld0JveD0iMCAwIDIwMCAxMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxkZWZzPgo8bGluZWFyR3JhZGllbnQgaWQ9ImxpdmVHcmFkaWVudCIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMTAwJSI+CjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiNGRjAwMDAiLz4KPHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSIjQ0MwMDAwIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPHJlY3Qgd2lkdGg9IjIwMCIgaGVpZ2h0PSIxMjAiIGZpbGw9InVybCgjbGl2ZUdyYWRpZW50KSIvPgo8dGV4dCB4PSIxMDAiIHk9IjYwIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMjQiIGZvbnQtd2VpZ2h0PSJib2xkIiBmaWxsPSJ3aGl0ZSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+55m75b2VPC90ZXh0Pgo8c3ZnIHg9IjgwIiB5PSIzMCIgd2lkdGg9IjQwIiBoZWlnaHQ9IjQwIiBmaWxsPSIjRkZGRkZGIj4KPGNpcmNsZSBjeD0iMjAiIGN5PSIyMCIgcj0iMTgiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSIyIi8+CjxwYXRoIGQ9Ik0xMCAxMCBMMzAgMzAgTTMwIDEwIEwxMCAzMCIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjIiLz4KPC9zdmc+Cjwvc3ZnPgo=",
|
|||
|
description: "直播服務",
|
|||
|
category: "streaming"
|
|||
|
},
|
|||
|
{
|
|||
|
id: 8,
|
|||
|
name: "陪玩",
|
|||
|
nameEn: "Gaming Companion",
|
|||
|
image: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjEyMCIgdmlld0JveD0iMCAwIDIwMCAxMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxkZWZzPgo8bGluZWFyR3JhZGllbnQgaWQ9ImNvbXBhbm9uR3JhZGllbnQiIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjEwMCUiPgo8c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSIjRkY2NkNDIi8+CjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI0ZGMDA5OSIvPgo8L2xpbmVhckdyYWRpZW50Pgo8L2RlZnM+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTIwIiBmaWxsPSJ1cmwoI2NvbXBhbm9uR3JhZGllbnQpIi8+Cjx0ZXh0IHg9IjEwMCIgeT0iNjAiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIyNCIgZm9udC13ZWlnaHQ9ImJvbGQiIGZpbGw9IndoaXRlIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj7mh6Dmh6A8L3RleHQ+CjxzdmcgeD0iODAiIHk9IjMwIiB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIGZpbGw9IiNGRkZGRkYiPgo8Y2lyY2xlIGN4PSIyMCIgY3k9IjIwIiByPSIxOCIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjIiLz4KPHBhdGggZD0iTTEwIDEwIEwzMCAzMCBNMzAgMTAgTDEwIDMwIiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS13aWR0aD0iMiIvPgo8L3N2Zz4KPC9zdmc+Cg==",
|
|||
|
description: "遊戲陪玩服務",
|
|||
|
category: "companion"
|
|||
|
}
|
|||
|
]);
|
|||
|
|
|||
|
// Services drag state
|
|||
|
const isDragging = ref(false);
|
|||
|
const startX = ref(0);
|
|||
|
const scrollLeft = ref(0);
|
|||
|
const servicesContainer = ref<HTMLElement | null>(null);
|
|||
|
|
|||
|
// 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
|
|||
|
};
|
|||
|
|
|||
|
// Handle more button click
|
|||
|
const handleMoreClick = (streamer: any) => {
|
|||
|
console.log(`查看更多: ${streamer.name}`);
|
|||
|
// TODO: Open detailed streamer page
|
|||
|
};
|
|||
|
|
|||
|
// Handle profile button click
|
|||
|
const handleProfileClick = (streamer: any) => {
|
|||
|
console.log(`前往主播主頁: ${streamer.name}`);
|
|||
|
// TODO: Navigate to streamer profile page
|
|||
|
};
|
|||
|
|
|||
|
// Handle service click
|
|||
|
const handleServiceClick = (service: any) => {
|
|||
|
console.log(`點擊服務: ${service.name}`);
|
|||
|
// TODO: Navigate to service page or open service modal
|
|||
|
};
|
|||
|
|
|||
|
// Handle service drag functionality
|
|||
|
const handleMouseDown = (e: MouseEvent) => {
|
|||
|
if (!servicesContainer.value) return;
|
|||
|
|
|||
|
isDragging.value = true;
|
|||
|
startX.value = e.pageX - servicesContainer.value.offsetLeft;
|
|||
|
scrollLeft.value = servicesContainer.value.scrollLeft;
|
|||
|
|
|||
|
// Prevent text selection while dragging
|
|||
|
e.preventDefault();
|
|||
|
};
|
|||
|
|
|||
|
const handleMouseMove = (e: MouseEvent) => {
|
|||
|
if (!isDragging.value || !servicesContainer.value) return;
|
|||
|
|
|||
|
e.preventDefault();
|
|||
|
const x = e.pageX - servicesContainer.value.offsetLeft;
|
|||
|
const walk = (x - startX.value) * 2; // Multiply for faster scrolling
|
|||
|
servicesContainer.value.scrollLeft = scrollLeft.value - walk;
|
|||
|
};
|
|||
|
|
|||
|
const handleMouseUp = () => {
|
|||
|
isDragging.value = false;
|
|||
|
};
|
|||
|
|
|||
|
const handleMouseLeave = () => {
|
|||
|
isDragging.value = false;
|
|||
|
};
|
|||
|
|
|||
|
// Handle more services button click
|
|||
|
const handleMoreServicesClick = () => {
|
|||
|
console.log('點擊更多服務');
|
|||
|
// TODO: Navigate to services page
|
|||
|
};
|
|||
|
|
|||
|
|
|||
|
onMounted(() => {
|
|||
|
startCarousel();
|
|||
|
});
|
|||
|
|
|||
|
onUnmounted(() => {
|
|||
|
stopCarousel();
|
|||
|
});
|
|||
|
</script>
|
|||
|
|
|||
|
<template>
|
|||
|
<div class="news-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">
|
|||
|
<img :src="stream.image" :alt="`Stream ${stream.id}`" />
|
|||
|
</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>
|
|||
|
|
|||
|
<!-- Recommended Services -->
|
|||
|
<div class="services-section">
|
|||
|
<div class="services-header">
|
|||
|
<h3 class="section-title">🎯 {{ t('livestream.recommendedServices') }}</h3>
|
|||
|
<button class="more-services-btn" @click="handleMoreServicesClick">
|
|||
|
更多
|
|||
|
</button>
|
|||
|
</div>
|
|||
|
|
|||
|
<div
|
|||
|
ref="servicesContainer"
|
|||
|
class="services-scroll-container"
|
|||
|
@mousedown="handleMouseDown"
|
|||
|
@mousemove="handleMouseMove"
|
|||
|
@mouseup="handleMouseUp"
|
|||
|
@mouseleave="handleMouseLeave"
|
|||
|
:class="{ 'dragging': isDragging }"
|
|||
|
>
|
|||
|
<div class="services-grid">
|
|||
|
<div
|
|||
|
v-for="service in recommendedServices"
|
|||
|
:key="service.id"
|
|||
|
class="service-card"
|
|||
|
@click="handleServiceClick(service)"
|
|||
|
>
|
|||
|
<div class="service-image">
|
|||
|
<img :src="service.image" :alt="service.name" />
|
|||
|
</div>
|
|||
|
<div class="service-label">{{ service.name }}</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<!-- Popular Streamers -->
|
|||
|
<div class="streamers-section">
|
|||
|
<h3 class="section-title">⭐ {{ t('livestream.popularStreamers') }}</h3>
|
|||
|
<div class="streamers-grid">
|
|||
|
<StreamerFlipCard
|
|||
|
v-for="streamer in popularStreamers"
|
|||
|
:key="streamer.id"
|
|||
|
:streamer="streamer"
|
|||
|
@click="handleStreamerClick"
|
|||
|
@more="handleMoreClick"
|
|||
|
@profile="handleProfileClick"
|
|||
|
/>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div> <!-- End home-view -->
|
|||
|
</div>
|
|||
|
</template>
|
|||
|
|
|||
|
<style scoped>
|
|||
|
.news-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%;
|
|||
|
aspect-ratio: 16/9;
|
|||
|
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%;
|
|||
|
position: relative;
|
|||
|
cursor: pointer;
|
|||
|
transition: transform 0.3s ease;
|
|||
|
overflow: hidden;
|
|||
|
}
|
|||
|
|
|||
|
.stream-image-card img {
|
|||
|
width: 100%;
|
|||
|
height: 100%;
|
|||
|
object-fit: cover;
|
|||
|
object-position: center;
|
|||
|
transition: transform 0.3s ease;
|
|||
|
}
|
|||
|
|
|||
|
.stream-image-card:hover img {
|
|||
|
transform: scale(1.05);
|
|||
|
}
|
|||
|
|
|||
|
@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);
|
|||
|
}
|
|||
|
|
|||
|
.services-section {
|
|||
|
margin-bottom: 32px;
|
|||
|
}
|
|||
|
|
|||
|
.services-header {
|
|||
|
display: flex;
|
|||
|
justify-content: space-between;
|
|||
|
align-items: center;
|
|||
|
margin-bottom: 20px;
|
|||
|
}
|
|||
|
|
|||
|
.more-services-btn {
|
|||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|||
|
color: white;
|
|||
|
border: none;
|
|||
|
padding: 8px 16px;
|
|||
|
border-radius: 20px;
|
|||
|
font-size: 14px;
|
|||
|
font-weight: 600;
|
|||
|
cursor: pointer;
|
|||
|
transition: all 0.3s ease;
|
|||
|
}
|
|||
|
|
|||
|
.more-services-btn:hover {
|
|||
|
transform: translateY(-2px);
|
|||
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
|||
|
}
|
|||
|
|
|||
|
.services-scroll-container {
|
|||
|
overflow-x: auto;
|
|||
|
overflow-y: hidden;
|
|||
|
cursor: grab;
|
|||
|
border-radius: 12px;
|
|||
|
scrollbar-width: none; /* Firefox */
|
|||
|
-ms-overflow-style: none; /* IE and Edge */
|
|||
|
}
|
|||
|
|
|||
|
.services-scroll-container::-webkit-scrollbar {
|
|||
|
display: none; /* Chrome, Safari, Opera */
|
|||
|
}
|
|||
|
|
|||
|
.services-scroll-container.dragging {
|
|||
|
cursor: grabbing;
|
|||
|
user-select: none;
|
|||
|
}
|
|||
|
|
|||
|
.services-grid {
|
|||
|
display: flex;
|
|||
|
gap: 24px;
|
|||
|
padding: 0 12px;
|
|||
|
min-width: max-content;
|
|||
|
}
|
|||
|
|
|||
|
.service-card {
|
|||
|
background: white;
|
|||
|
border-radius: 12px;
|
|||
|
overflow: hidden;
|
|||
|
cursor: pointer;
|
|||
|
transition: all 0.3s ease;
|
|||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|||
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
|||
|
width: 200px;
|
|||
|
flex-shrink: 0;
|
|||
|
}
|
|||
|
|
|||
|
.service-card:hover {
|
|||
|
transform: translateY(-4px);
|
|||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
|||
|
}
|
|||
|
|
|||
|
.service-image {
|
|||
|
width: 100%;
|
|||
|
height: 120px;
|
|||
|
overflow: hidden;
|
|||
|
position: relative;
|
|||
|
}
|
|||
|
|
|||
|
.service-image img {
|
|||
|
width: 100%;
|
|||
|
height: 100%;
|
|||
|
object-fit: cover;
|
|||
|
object-position: center;
|
|||
|
}
|
|||
|
|
|||
|
.service-label {
|
|||
|
padding: 12px 16px;
|
|||
|
text-align: center;
|
|||
|
font-size: 14px;
|
|||
|
font-weight: 600;
|
|||
|
color: #333333;
|
|||
|
background: white;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
.streamers-section {
|
|||
|
flex: 1;
|
|||
|
}
|
|||
|
|
|||
|
.streamers-grid {
|
|||
|
display: grid;
|
|||
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|||
|
gap: 24px;
|
|||
|
align-items: start;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* 響應式網格佈局 */
|
|||
|
/* 響應式設計 */
|
|||
|
@media (max-width: 1200px) {
|
|||
|
.services-grid {
|
|||
|
gap: 20px;
|
|||
|
padding: 0 16px;
|
|||
|
}
|
|||
|
|
|||
|
.service-card {
|
|||
|
width: 250px;
|
|||
|
}
|
|||
|
|
|||
|
.streamers-grid {
|
|||
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|||
|
gap: 20px;
|
|||
|
}
|
|||
|
|
|||
|
.carousel-container {
|
|||
|
aspect-ratio: 16/9;
|
|||
|
}
|
|||
|
|
|||
|
.carousel-btn {
|
|||
|
width: 40px;
|
|||
|
height: 40px;
|
|||
|
font-size: 20px;
|
|||
|
}
|
|||
|
|
|||
|
.carousel-btn.prev {
|
|||
|
left: 12px;
|
|||
|
}
|
|||
|
|
|||
|
.carousel-btn.next {
|
|||
|
right: 12px;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
@media (max-width: 768px) {
|
|||
|
.news-hub {
|
|||
|
padding: 16px;
|
|||
|
}
|
|||
|
|
|||
|
.hub-title {
|
|||
|
font-size: 24px;
|
|||
|
}
|
|||
|
|
|||
|
.hub-subtitle {
|
|||
|
font-size: 14px;
|
|||
|
}
|
|||
|
|
|||
|
.services-grid {
|
|||
|
gap: 16px;
|
|||
|
padding: 0 12px;
|
|||
|
}
|
|||
|
|
|||
|
.service-card {
|
|||
|
width: 180px;
|
|||
|
}
|
|||
|
|
|||
|
.service-image {
|
|||
|
height: 100px;
|
|||
|
}
|
|||
|
|
|||
|
.service-label {
|
|||
|
padding: 10px 12px;
|
|||
|
font-size: 13px;
|
|||
|
}
|
|||
|
|
|||
|
.more-services-btn {
|
|||
|
padding: 6px 12px;
|
|||
|
font-size: 12px;
|
|||
|
}
|
|||
|
|
|||
|
.streamers-grid {
|
|||
|
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
|||
|
gap: 16px;
|
|||
|
}
|
|||
|
|
|||
|
.carousel-container {
|
|||
|
aspect-ratio: 16/9;
|
|||
|
border-radius: 8px;
|
|||
|
}
|
|||
|
|
|||
|
.carousel-btn {
|
|||
|
width: 36px;
|
|||
|
height: 36px;
|
|||
|
font-size: 18px;
|
|||
|
}
|
|||
|
|
|||
|
.carousel-btn.prev {
|
|||
|
left: 8px;
|
|||
|
}
|
|||
|
|
|||
|
.carousel-btn.next {
|
|||
|
right: 8px;
|
|||
|
}
|
|||
|
|
|||
|
.carousel-indicators {
|
|||
|
bottom: 12px;
|
|||
|
gap: 6px;
|
|||
|
}
|
|||
|
|
|||
|
.indicator {
|
|||
|
width: 10px;
|
|||
|
height: 10px;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
@media (max-width: 480px) {
|
|||
|
.news-hub {
|
|||
|
padding: 12px;
|
|||
|
}
|
|||
|
|
|||
|
.hub-title {
|
|||
|
font-size: 20px;
|
|||
|
margin-bottom: 8px;
|
|||
|
}
|
|||
|
|
|||
|
.hub-subtitle {
|
|||
|
font-size: 13px;
|
|||
|
margin-bottom: 20px;
|
|||
|
}
|
|||
|
|
|||
|
.services-grid {
|
|||
|
gap: 12px;
|
|||
|
padding: 0 8px;
|
|||
|
}
|
|||
|
|
|||
|
.service-card {
|
|||
|
width: 150px;
|
|||
|
}
|
|||
|
|
|||
|
.service-image {
|
|||
|
height: 80px;
|
|||
|
}
|
|||
|
|
|||
|
.service-label {
|
|||
|
padding: 8px 10px;
|
|||
|
font-size: 12px;
|
|||
|
}
|
|||
|
|
|||
|
.more-services-btn {
|
|||
|
padding: 4px 8px;
|
|||
|
font-size: 11px;
|
|||
|
}
|
|||
|
|
|||
|
.streamers-grid {
|
|||
|
grid-template-columns: 1fr;
|
|||
|
gap: 16px;
|
|||
|
}
|
|||
|
|
|||
|
.carousel-container {
|
|||
|
aspect-ratio: 16/9;
|
|||
|
border-radius: 6px;
|
|||
|
}
|
|||
|
|
|||
|
.carousel-btn {
|
|||
|
width: 32px;
|
|||
|
height: 32px;
|
|||
|
font-size: 16px;
|
|||
|
}
|
|||
|
|
|||
|
.carousel-btn.prev {
|
|||
|
left: 6px;
|
|||
|
}
|
|||
|
|
|||
|
.carousel-btn.next {
|
|||
|
right: 6px;
|
|||
|
}
|
|||
|
|
|||
|
.carousel-indicators {
|
|||
|
bottom: 8px;
|
|||
|
gap: 4px;
|
|||
|
}
|
|||
|
|
|||
|
.indicator {
|
|||
|
width: 8px;
|
|||
|
height: 8px;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* 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>
|