Files
ai_dianshang/web/assets/js/live-float.js
2025-11-28 15:18:10 +08:00

774 lines
28 KiB
JavaScript
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.

// 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);
}
}