Files
ai_dianshang/web/assets/js/live-float.js

774 lines
28 KiB
JavaScript
Raw Permalink Normal View History

2025-11-28 15:18:10 +08:00
// Live Float Button and Modal JavaScript
let currentLiveStream = null; // 当前直播源数据
$(document).ready(function() {
initLiveFloat();
});
let liveStatsInterval = null;
let randomMessageInterval = null;
let floatViewersInterval = null;
let danmakuInterval = null;
function initLiveFloat() {
// 先加载直播源数据
loadLiveStreamData();
bindLiveFloatEvents();
// 监听语言切换
$(document).on('languageChanged', function() {
if ($('#liveModal').hasClass('active')) {
loadChatMessages();
loadLiveProducts();
}
});
}
// 加载直播源数据
function loadLiveStreamData() {
LiveStreamAPI.getActiveLiveStreams()
.then(data => {
if (data && data.length > 0) {
// 获取第一个有stream_url的直播源
currentLiveStream = data.find(stream => stream.stream_url);
if (currentLiveStream) {
// 有直播源,显示浮窗并更新数据
updateFloatButton(currentLiveStream);
$('#liveFloatBtn').show();
// 启动悬浮窗观看人数动态更新
updateFloatViewers();
floatViewersInterval = setInterval(updateFloatViewers, 3000);
} else {
// 没有可用的直播源,显示未开播
showOfflineFloat();
}
} else {
// 没有直播源数据,显示未开播
showOfflineFloat();
}
})
.catch(error => {
console.error('加载直播源失败:', error);
// 加载失败,隐藏浮窗
$('#liveFloatBtn').hide();
});
}
// 更新浮窗按钮数据
function updateFloatButton(stream) {
// 更新标题
$('.live-float-title').text(stream.title);
// 更新平台名称
$('.live-float-name').text(stream.platform + '官方直播');
// 如果有封面图,更新封面
if (stream.cover_image) {
const $video = $('.live-float-video');
$video.replaceWith(`<img src="${stream.cover_image}" alt="${stream.title}" class="live-float-video" style="width: 100%; height: 100%; object-fit: cover;">`);
}
}
// 显示未开播状态
function showOfflineFloat() {
$('#liveFloatBtn').show().addClass('offline');
// 移除LIVE徽章
$('.live-float-badge-top').html(`
<span class="offline-badge-float">未开播</span>
`);
// 更新标题
$('.live-float-title').text('暂未开播');
$('.live-float-name').text('敬请期待');
$('.live-float-desc').text('主播休息中...');
// 清除定时器
if (floatViewersInterval) {
clearInterval(floatViewersInterval);
floatViewersInterval = null;
}
}
// 绑定事件
function bindLiveFloatEvents() {
// 点击悬浮按钮打开直播
$('#liveFloatBtn').on('click', function() {
if (currentLiveStream && currentLiveStream.stream_url) {
// 有直播源,打开直播弹窗
openLiveModal();
} else {
// 未开播,显示提示
if (typeof Toast !== 'undefined') {
Toast.show('直播暂未开始,敬请期待', 'info');
} else {
alert('直播暂未开始,敬请期待');
}
}
});
// 点击关闭按钮
$('#liveModalClose').on('click', function() {
closeLiveModal();
});
// 点击模态框背景关闭
$('#liveModal').on('click', function(e) {
if (e.target.id === 'liveModal') {
closeLiveModal();
}
});
// Tab切换
$('.tab-btn').on('click', function() {
const tabName = $(this).data('tab');
switchTab(tabName);
});
// 点赞按钮
$('#likeBtn').on('click', function() {
handleLike();
});
// 发送消息
$('#sendMessageBtn').on('click', function() {
sendMessage();
});
$('#chatInput').on('keypress', function(e) {
if (e.which === 13) {
sendMessage();
}
});
// 产品点击
$(document).on('click', '.live-product-item', function() {
const productId = $(this).data('product-id');
// 关闭直播窗口并跳转到商品详情
closeLiveModal();
setTimeout(() => {
window.location.href = `product-detail.html?id=${productId}`;
}, 300);
});
$(document).on('click', '.live-product-btn', function(e) {
e.stopPropagation();
const productId = $(this).closest('.live-product-item').data('product-id');
addProductToCart(productId);
});
}
// 打开直播模态框
function openLiveModal() {
if (!currentLiveStream || !currentLiveStream.stream_url) {
if (typeof Toast !== 'undefined') {
Toast.show('直播暂未开始,敬请期待', 'info');
} else {
alert('直播暂未开始,敬请期待');
}
return;
}
$('#liveModal').addClass('active');
$('body').css('overflow', 'hidden');
// 更新主播信息
$('#liveHostName').text(currentLiveStream.title);
$('#liveViewerCount').text(formatViewCount(currentLiveStream.view_count || 1234));
// 显示并绑定跳转按钮
const $gotoBtn = $('#gotoPlatformBtnTop');
$('#platformNameTop').text(currentLiveStream.platform);
$gotoBtn.show().off('click').on('click', function() {
window.open(currentLiveStream.stream_url, '_blank');
});
// 更新视频源
const videoContainer = $('.live-video-container');
videoContainer.html(`
<iframe
src="${currentLiveStream.stream_url}"
class="live-video-player"
frameborder="0"
allowfullscreen
allow="autoplay; fullscreen; picture-in-picture"
scrolling="no"
style="width: 100%; height: 100%; position: absolute; top: 0; left: 0; pointer-events: auto;">
</iframe>
<div class="live-top-bar">
<div class="live-host-info">
<div class="host-avatar">
<img src="https://picsum.photos/40/40?random=host" alt="主播">
</div>
<div class="host-details">
<div class="host-name">${currentLiveStream.title}</div>
<div class="host-viewers">
<span>${formatViewCount(currentLiveStream.view_count || 1234)}</span> 线
</div>
</div>
<button class="btn-follow">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
关注
</button>
</div>
<button class="btn-goto-platform-top" onclick="window.open('${currentLiveStream.stream_url}', '_blank')">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
<polyline points="15 3 21 3 21 9"></polyline>
<line x1="10" y1="14" x2="21" y2="3"></line>
</svg>
前往${currentLiveStream.platform}观看
</button>
</div>
<div class="live-interaction-bar">
<button class="interaction-btn" id="likeBtn" title="点赞">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
</svg>
<span id="likeCount">1.2W</span>
</button>
<button class="interaction-btn" title="评论">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
</svg>
<span id="commentCount">8923</span>
</button>
<button class="interaction-btn" title="分享">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="18" cy="5" r="3"></circle>
<circle cx="6" cy="12" r="3"></circle>
<circle cx="18" cy="19" r="3"></circle>
<line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line>
<line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line>
</svg>
<span>分享</span>
</button>
</div>
<div class="danmaku-container" id="danmakuContainer"></div>
<div class="live-chat-preview" id="chatPreview"></div>
`);
// 绑定点赞按钮
$('#likeBtn').on('click', function() {
handleLike();
});
// 增加观看次数
LiveStreamAPI.incrementViewCount(currentLiveStream.id)
.then(() => {
console.log('观看次数已增加');
})
.catch(error => {
console.error('增加观看次数失败:', error);
});
// 加载内容
loadChatMessages();
loadLiveProducts();
// 开始定时更新
randomMessageInterval = setInterval(addRandomMessage, 10000);
danmakuInterval = setInterval(addRandomDanmaku, 3000);
}
// 格式化观看人数
function formatViewCount(count) {
if (!count || count === 0) return '0';
if (count < 1000) return count.toString();
if (count < 10000) return (count / 1000).toFixed(1) + 'K';
return (count / 10000).toFixed(1) + 'W';
}
// 关闭直播模态框
function closeLiveModal() {
$('#liveModal').removeClass('active');
$('body').css('overflow', '');
// 恢复原始视频容器结构移除controls属性
const videoContainer = $('.live-video-container');
videoContainer.html(`
<video id="liveVideo" class="live-video-player" autoplay muted loop playsinline>
<source src="https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/720/Big_Buck_Bunny_720_10s_1MB.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
<div class="live-indicator">
<span class="live-dot"></span>
<span data-i18n="live_status">🔴 直播中</span>
</div>
<div class="danmaku-container" id="danmakuContainer"></div>
`);
// 清除定时器
if (liveStatsInterval) {
clearInterval(liveStatsInterval);
liveStatsInterval = null;
}
if (randomMessageInterval) {
clearInterval(randomMessageInterval);
randomMessageInterval = null;
}
if (danmakuInterval) {
clearInterval(danmakuInterval);
danmakuInterval = null;
}
// 清除弹幕
$('#danmakuContainer').empty();
}
// Tab切换
function switchTab(tabName) {
$('.tab-btn').removeClass('active');
$(`.tab-btn[data-tab="${tabName}"]`).addClass('active');
$('.tab-content').removeClass('active');
$(`#${tabName}Tab`).addClass('active');
}
// 加载聊天消息
function loadChatMessages() {
const lang = i18n.currentLang;
const messages = [
{
type: 'system',
text: lang === 'zh-CN' ? '欢迎来到直播间!' :
lang === 'en-US' ? 'Welcome to the live stream!' :
'ライブ配信へようこそ!',
time: '10:00'
},
{
username: lang === 'zh-CN' ? '小明' : lang === 'en-US' ? 'Mike' : 'タロウ',
text: lang === 'zh-CN' ? '这款拼图看起来很不错!' :
lang === 'en-US' ? 'This puzzle looks great!' :
'このパズルは素晴らしいですね!',
time: '10:05'
},
{
username: lang === 'zh-CN' ? '小红' : lang === 'en-US' ? 'Lucy' : 'ハナコ',
text: lang === 'zh-CN' ? '适合多大年龄的孩子?' :
lang === 'en-US' ? 'What age is it suitable for?' :
'何歳の子供に適していますか?',
time: '10:06'
},
{
username: lang === 'zh-CN' ? '主播' : lang === 'en-US' ? 'Host' : 'ホスト',
text: lang === 'zh-CN' ? '这款拼图适合3-6岁的孩子可以培养动手能力和空间想象力' :
lang === 'en-US' ? 'This puzzle is suitable for children aged 3-6 and helps develop hands-on skills and spatial imagination!' :
'このパズルは3〜6歳の子供に適しており、実践的なスキルと空間想像力を養うのに役立ちます',
time: '10:07'
},
{
username: lang === 'zh-CN' ? '李华' : lang === 'en-US' ? 'David' : 'ダビデ',
text: lang === 'zh-CN' ? '价格很优惠,已经下单了!' :
lang === 'en-US' ? 'Great price, just ordered!' :
'価格もお得で、注文しました!',
time: '10:08'
},
{
username: lang === 'zh-CN' ? '王芳' : lang === 'en-US' ? 'Sarah' : 'サラ',
text: lang === 'zh-CN' ? '材质安全吗?' :
lang === 'en-US' ? 'Is the material safe?' :
'素材は安全ですか?',
time: '10:09'
},
{
username: lang === 'zh-CN' ? '主播' : lang === 'en-US' ? 'Host' : 'ホスト',
text: lang === 'zh-CN' ? '所有产品都通过了安全认证,使用环保材料,家长可以放心!' :
lang === 'en-US' ? 'All products are safety certified and made from eco-friendly materials. Parents can rest assured!' :
'すべての製品は安全認証を受けており、環境に優しい素材を使用しています。親御さんも安心です!',
time: '10:10'
}
];
let chatHtml = '';
messages.forEach(msg => {
if (msg.type === 'system') {
chatHtml += `
<div class="chat-message system">
<div class="chat-text">${msg.text}</div>
</div>
`;
} else {
chatHtml += `
<div class="chat-message">
<div class="chat-message-header">
<span class="chat-username">${msg.username}</span>
<span class="chat-time">${msg.time}</span>
</div>
<div class="chat-text">${msg.text}</div>
</div>
`;
}
});
$('#chatMessages').html(chatHtml);
scrollChatToBottom();
}
// 加载直播商品
function loadLiveProducts() {
const lang = i18n.currentLang;
const products = [
{
id: 1,
name: '200片拼图 - 海洋世界',
name_en: '200 Piece Puzzle - Ocean World',
name_ja: '200ピースパズル - 海の世界',
price: 19.99,
originalPrice: 29.99,
image: 'https://picsum.photos/200/200?random=live1'
},
{
id: 2,
name: '艺术绘画套装',
name_en: 'Art Painting Set',
name_ja: 'アート絵画セット',
price: 24.99,
originalPrice: 34.99,
image: 'https://picsum.photos/200/200?random=live2'
},
{
id: 3,
name: '木质积木 - 100块',
name_en: 'Wooden Blocks - 100 Pieces',
name_ja: '木製ブロック - 100ピース',
price: 29.99,
originalPrice: 39.99,
image: 'https://picsum.photos/200/200?random=live3'
},
{
id: 4,
name: '磁力片建构玩具',
name_en: 'Magnetic Building Tiles',
name_ja: 'マグネットタイル',
price: 34.99,
originalPrice: 49.99,
image: 'https://picsum.photos/200/200?random=live4'
},
{
id: 5,
name: '儿童桌游套装',
name_en: 'Kids Board Game Set',
name_ja: '子供用ボードゲームセット',
price: 27.99,
originalPrice: 37.99,
image: 'https://picsum.photos/200/200?random=live5'
},
{
id: 6,
name: '手工贴纸书',
name_en: 'Sticker Activity Book',
name_ja: 'ステッカーブック',
price: 12.99,
originalPrice: 18.99,
image: 'https://picsum.photos/200/200?random=live6'
}
];
let productsHtml = '';
products.forEach(product => {
const productName = lang === 'zh-CN' ? product.name :
lang === 'en-US' ? product.name_en :
product.name_ja;
const btnText = i18n.t('add_to_cart');
productsHtml += `
<div class="live-product-item" data-product-id="${product.id}">
<div class="live-product-image">
<img src="${product.image}" alt="${productName}">
</div>
<div class="live-product-info">
<div class="live-product-title">${productName}</div>
<div class="live-product-price">
<span class="live-product-current-price">$${product.price.toFixed(2)}</span>
<span class="live-product-original-price">$${product.originalPrice.toFixed(2)}</span>
</div>
<button class="live-product-btn">${btnText}</button>
</div>
</div>
`;
});
$('#liveProducts').html(productsHtml);
}
// 更新直播统计
function updateLiveStats() {
const currentViewers = parseInt($('#viewerCount').text().replace(/,/g, ''));
const currentLikes = parseInt($('#likeCount').text().replace(/,/g, ''));
const currentMessages = parseInt($('#messageCount').text().replace(/,/g, ''));
// 随机增加数值
const newViewers = currentViewers + Math.floor(Math.random() * 20) - 5;
const newLikes = currentLikes + Math.floor(Math.random() * 15);
const newMessages = currentMessages + Math.floor(Math.random() * 5);
$('#viewerCount').text(Math.max(1000, newViewers).toLocaleString());
$('#likeCount').text(Math.max(500, newLikes).toLocaleString());
$('#messageCount').text(Math.max(50, newMessages).toLocaleString());
}
// 发送消息
function sendMessage() {
const message = $('#chatInput').val().trim();
if (!message) {
return;
}
const lang = i18n.currentLang;
const username = lang === 'zh-CN' ? '我' : lang === 'en-US' ? 'Me' : '私';
const currentTime = new Date();
const timeString = `${currentTime.getHours()}:${currentTime.getMinutes().toString().padStart(2, '0')}`;
// 添加到聊天记录
const messageHtml = `
<div class="chat-message">
<div class="chat-message-header">
<span class="chat-username">${username}</span>
<span class="chat-time">${timeString}</span>
</div>
<div class="chat-text">${message}</div>
</div>
`;
$('#chatMessages').append(messageHtml);
// 如果开启了弹幕,也显示在弹幕区
if ($('#danmakuToggle').is(':checked')) {
createDanmaku(message);
}
$('#chatInput').val('');
scrollChatToBottom();
// 更新消息计数
const currentCount = parseInt($('#messageCount').text().replace(/,/g, ''));
$('#messageCount').text((currentCount + 1).toLocaleString());
}
// 添加随机消息
function addRandomMessage() {
const lang = i18n.currentLang;
const usernames = lang === 'zh-CN' ? ['张三', '李四', '王五', '赵六'] :
lang === 'en-US' ? ['John', 'Jane', 'Bob', 'Alice'] :
['太郎', '花子', '次郎', '美咲'];
const messages = lang === 'zh-CN' ?
['这个产品真不错!', '价格很实惠', '已经下单了', '质量怎么样?', '有优惠码吗?', '主播讲得很详细', '这个颜色好看'] :
lang === 'en-US' ?
['This product is great!', 'Great price', 'Just ordered', 'How\'s the quality?', 'Any discount codes?', 'Very informative', 'Love this color'] :
['この製品は素晴らしいです!', '価格もお得', '注文しました', '品質はどうですか?', '割引コードはありますか?', 'とても詳しい', 'この色いいね'];
const randomUsername = usernames[Math.floor(Math.random() * usernames.length)];
const randomMessage = messages[Math.floor(Math.random() * messages.length)];
const currentTime = new Date();
const timeString = `${currentTime.getHours()}:${currentTime.getMinutes().toString().padStart(2, '0')}`;
const messageHtml = `
<div class="chat-message">
<div class="chat-message-header">
<span class="chat-username">${randomUsername}</span>
<span class="chat-time">${timeString}</span>
</div>
<div class="chat-text">${randomMessage}</div>
</div>
`;
$('#chatMessages').append(messageHtml);
scrollChatToBottom();
// 更新消息计数
const currentCount = parseInt($('#messageCount').text().replace(/,/g, ''));
$('#messageCount').text((currentCount + 1).toLocaleString());
}
// 滚动聊天到底部
function scrollChatToBottom() {
const chatMessages = document.getElementById('chatMessages');
if (chatMessages) {
chatMessages.scrollTop = chatMessages.scrollHeight;
}
}
// 更新悬浮窗观看人数
function updateFloatViewers() {
const lang = i18n.currentLang;
const viewers = ['1.8W', '1.9W', '2.0W', '2.1W', '1.7W'];
const randomViewers = viewers[Math.floor(Math.random() * viewers.length)];
const viewersText = lang === 'zh-CN' ? `${randomViewers}观看` :
lang === 'en-US' ? `${randomViewers} watching` :
`${randomViewers}視聴中`;
$('#floatViewers').text(viewersText);
}
// 处理点赞
function handleLike() {
const $likeBtn = $('#likeBtn');
$likeBtn.addClass('liked');
// 更新点赞数
const currentLikes = parseInt($('#likeCount').text().replace(/,/g, ''));
$('#likeCount').text((currentLikes + 1).toLocaleString());
// 移除动画类
setTimeout(() => {
$likeBtn.removeClass('liked');
}, 500);
// 生成点赞动画
createLikeAnimation();
}
// 创建点赞动画
function createLikeAnimation() {
const $container = $('.live-video-container');
const colors = ['#ff6b6b', '#ff8787', '#ffa5a5', '#ff5252', '#ee5a6f'];
for (let i = 0; i < 3; i++) {
setTimeout(() => {
const $heart = $('<div class="like-animation">❤️</div>');
$heart.css({
position: 'absolute',
bottom: '20%',
right: Math.random() * 30 + 5 + '%',
fontSize: Math.random() * 15 + 25 + 'px',
color: colors[Math.floor(Math.random() * colors.length)],
animation: 'likeFloat 2.5s ease-out forwards',
zIndex: 100,
pointerEvents: 'none'
});
$container.append($heart);
setTimeout(() => {
$heart.remove();
}, 2500);
}, i * 200);
}
}
// 添加随机弹幕
function addRandomDanmaku() {
const lang = i18n.currentLang;
const messages = lang === 'zh-CN' ?
['这个好看!', '已经下单了', '主播讲得很好', '价格优惠', '喜欢这款', '质量怎么样?', '太棒了!', '有优惠吗?'] :
lang === 'en-US' ?
['Love this!', 'Just ordered', 'Great demo', 'Good price', 'Like this one', 'How\'s the quality?', 'Amazing!', 'Any discounts?'] :
['これいい!', '注文しました', '素晴らしい', '価格が良い', '好きです', '品質は?', '最高!', '割引は?'];
const randomMessage = messages[Math.floor(Math.random() * messages.length)];
createDanmaku(randomMessage);
}
// 创建弹幕
function createDanmaku(text) {
const $container = $('#danmakuContainer');
const containerWidth = $container.width();
const containerHeight = $container.height();
if (!containerWidth || !containerHeight) return;
const $danmaku = $('<div class="danmaku-item"></div>');
$danmaku.text(text);
// 随机高度位置
const top = Math.random() * (containerHeight - 50);
const duration = 6 + Math.random() * 2; // 6-8秒更快
$danmaku.css({
top: top + 'px',
left: containerWidth + 'px',
animationDuration: duration + 's'
});
$container.append($danmaku);
// 弹幕结束后移除
setTimeout(() => {
$danmaku.remove();
}, duration * 1000);
}
// 添加商品到购物车
function addProductToCart(productId) {
const lang = i18n.currentLang;
// 获取产品信息
const products = [
{
id: 1,
name: '200片拼图 - 海洋世界',
name_en: '200 Piece Puzzle - Ocean World',
name_ja: '200ピースパズル - 海の世界',
price: 19.99,
image: 'https://picsum.photos/200/200?random=live1'
},
{
id: 2,
name: '艺术绘画套装',
name_en: 'Art Painting Set',
name_ja: 'アート絵画セット',
price: 24.99,
image: 'https://picsum.photos/200/200?random=live2'
},
{
id: 3,
name: '木质积木 - 100块',
name_en: 'Wooden Blocks - 100 Pieces',
name_ja: '木製ブロック - 100ピース',
price: 29.99,
image: 'https://picsum.photos/200/200?random=live3'
},
{
id: 4,
name: '磁力片建构玩具',
name_en: 'Magnetic Building Tiles',
name_ja: 'マグネットタイル',
price: 34.99,
image: 'https://picsum.photos/200/200?random=live4'
},
{
id: 5,
name: '儿童桌游套装',
name_en: 'Kids Board Game Set',
name_ja: '子供用ボードゲームセット',
price: 27.99,
image: 'https://picsum.photos/200/200?random=live5'
},
{
id: 6,
name: '手工贴纸书',
name_en: 'Sticker Activity Book',
name_ja: 'ステッカーブック',
price: 12.99,
image: 'https://picsum.photos/200/200?random=live6'
}
];
const product = products.find(p => p.id === productId);
if (product) {
cart.addToCart(product);
const message = i18n.t('product_added_to_cart') ||
(lang === 'zh-CN' ? '商品已添加到购物车' :
lang === 'en-US' ? 'Product added to cart' :
'商品がカートに追加されました');
alert(message);
}
}