Files
Cola/chat-func-panel.js
2025-12-24 02:07:04 +08:00

768 lines
26 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.
/**
* 聊天页功能面板 + 展开输入(语音/多条消息/混合消息)
*/
import { calculateVoiceDuration, escapeHtml, sleep } from './utils.js';
import { showToast } from './toast.js';
import { sendMessage, sendPhotoMessage, sendBatchMessages, appendMusicCardMessage, currentChatIndex, appendMessage, showTypingIndicator, hideTypingIndicator, parseAiQuoteMessage, detectAiCallRequest } from './chat.js';
import { isInGroupChat, sendGroupMessage, sendGroupPhotoMessage, sendGroupBatchMessages, getCurrentGroupIndex, appendGroupMessage, showGroupTypingIndicator, hideGroupTypingIndicator, callGroupAI, enforceGroupChatMemberLimit, appendGroupMusicCardMessage } from './group-chat.js';
import { startVoiceCall } from './voice-call.js';
import { startVideoCall } from './video-call.js';
import { showMusicPanel, initMusicEvents } from './music.js';
import { showRedPacketPage } from './red-packet.js';
import { showTransferPage } from './transfer.js';
import { showGiftPage } from './gift.js';
import { getSettings, splitAIMessages } from './config.js';
import { refreshChatList } from './ui.js';
import { requestSave } from './save-manager.js';
import { callAI } from './ai.js';
import { showListenSearchPage, initListenTogether } from './listen-together.js';
let expandMode = null; // 'voice' | 'multi' | null
// 混合消息项: { type: 'text' | 'voice' | 'sticker' | 'photo', content: string }
let expandMsgItems = [{ type: 'text', content: '' }];
let funcPanelPage = 0;
let funcPanelInited = false;
// 临时存储待插入的表情URL
let pendingStickerIndex = -1;
let musicShareListenerInited = false;
function safeText(value) {
return value == null ? '' : String(value).trim();
}
function clipText(text, maxChars) {
const raw = safeText(text);
if (!raw) return '';
if (raw.length <= maxChars) return raw;
return raw.slice(0, maxChars - 1) + '…';
}
function clipLyrics(lyrics) {
const raw = safeText(lyrics);
if (!raw) return '';
// 移除时间标签,只保留歌词文本
const lines = raw.split(/\r?\n/)
.map(line => line.replace(/^\[\d{2}:\d{2}[.\d]*\]/g, '').trim())
.filter(line => line);
const limitedLines = lines.slice(0, 30).join('\n');
return clipText(limitedLines, 800);
}
function formatMusicShareMessage(song) {
const name = safeText(song?.name) || '未知歌曲';
const artist = safeText(song?.artist);
const lyrics = clipLyrics(song?.lyrics);
let message = `[分享音乐] ${name}`;
if (artist) message += ` - ${artist}`;
if (lyrics) message += `\n\n${lyrics}`;
return message;
}
function initMusicShareListener() {
if (musicShareListenerInited) return;
musicShareListenerInited = true;
document.addEventListener('music-share', async (e) => {
const song = e?.detail;
if (!song) return;
const settings = getSettings();
const groupIndex = getCurrentGroupIndex();
// 构建给AI的消息包含歌名歌手和歌词
const name = safeText(song?.name) || '未知歌曲';
const artist = safeText(song?.artist);
const lyrics = clipLyrics(song?.lyrics);
let aiMessage = `[分享音乐] ${name}`;
if (artist) aiMessage += ` - ${artist}`;
if (lyrics) aiMessage += `\n歌词:\n${lyrics}`;
const now = new Date();
const timeStr = `${now.getFullYear()}-${(now.getMonth()+1).toString().padStart(2,'0')}-${now.getDate().toString().padStart(2,'0')} ${now.getHours().toString().padStart(2,'0')}:${now.getMinutes().toString().padStart(2,'0')}`;
// 群聊分享音乐
if (groupIndex >= 0) {
const groupChat = settings.groupChats?.[groupIndex];
if (!groupChat) return;
if (!Array.isArray(groupChat.chatHistory)) {
groupChat.chatHistory = [];
}
// 显示音乐卡片
appendGroupMusicCardMessage('user', song);
// 保存到聊天历史
groupChat.chatHistory.push({
role: 'user',
content: aiMessage,
time: timeStr,
timestamp: Date.now(),
isMusic: true,
musicInfo: { name: song.name, artist: song.artist, platform: song.platform, cover: song.cover, id: song.id }
});
groupChat.lastMessage = `[音乐] ${name}`;
groupChat.lastMessageTime = Date.now();
requestSave();
refreshChatList();
// 获取成员信息
const { memberIds } = enforceGroupChatMemberLimit(groupChat);
const members = memberIds.map(id => settings.contacts.find(c => c.id === id)).filter(Boolean);
if (members.length === 0) {
showToast('群聊成员不存在', '⚠️');
return;
}
// 显示打字指示器
showGroupTypingIndicator(members[0]?.name, members[0]?.id);
try {
// 调用群聊AI
const responses = await callGroupAI(groupChat, members, aiMessage, []);
hideGroupTypingIndicator();
// 逐条显示AI回复
for (let i = 0; i < responses.length; i++) {
const resp = responses[i];
// 显示typing指示器并等待
showGroupTypingIndicator(resp.characterName, resp.characterId);
await sleep(800 + Math.random() * 400);
hideGroupTypingIndicator();
// 保存并显示消息
groupChat.chatHistory.push({
role: 'assistant',
content: resp.content,
time: timeStr,
timestamp: Date.now(),
characterName: resp.characterName,
characterId: resp.characterId
});
appendGroupMessage('assistant', resp.content, resp.characterName, resp.characterId);
}
if (responses.length > 0) {
const lastResp = responses[responses.length - 1];
groupChat.lastMessage = lastResp.content.length > 20 ? lastResp.content.substring(0, 20) + '...' : lastResp.content;
groupChat.lastMessageTime = Date.now();
}
requestSave();
refreshChatList();
} catch (err) {
hideGroupTypingIndicator();
console.error('[可乐] 群聊音乐分享AI回复失败:', err);
}
return;
}
// 单聊分享音乐
if (currentChatIndex < 0) return;
const contactIndex = currentChatIndex;
const contact = settings.contacts[contactIndex];
if (!contact) return;
if (!contact.chatHistory) {
contact.chatHistory = [];
}
// 显示音乐卡片
appendMusicCardMessage('user', song, contact);
// 保存到聊天历史
contact.chatHistory.push({
role: 'user',
content: aiMessage,
time: timeStr,
timestamp: Date.now(),
isMusic: true,
musicInfo: { name: song.name, artist: song.artist, platform: song.platform, cover: song.cover, id: song.id }
});
contact.lastMessage = `[音乐] ${name}`;
requestSave();
refreshChatList();
// 调用AI回复
showTypingIndicator(contact);
try {
const aiReply = await callAI(contact, aiMessage);
hideTypingIndicator();
if (aiReply) {
// 使用 splitAIMessages 分割AI回复
const aiMessages = splitAIMessages(aiReply);
let lastShownMessage = null;
for (let i = 0; i < aiMessages.length; i++) {
const rawMsg = aiMessages[i];
// 兼容 AI 发起通话请求(如:[通话请求] / [语音通话请求] / [视频通话请求]),不显示为文本
const callRequestType = detectAiCallRequest(rawMsg);
if (callRequestType === 'voice') {
startVoiceCall('ai', contactIndex);
break; // 通话请求必须单独一条
}
if (callRequestType === 'video') {
startVideoCall('ai', contactIndex);
break; // 通话请求必须单独一条
}
// 解析 [回复:xxx] 引用格式,避免把标记直接显示出来
const parsed = parseAiQuoteMessage(rawMsg, contact);
const msg = (parsed?.content || '').toString().trim();
const quote = parsed?.quote || null;
if (!msg) continue;
contact.chatHistory.push({
role: 'assistant',
content: msg,
time: timeStr,
timestamp: Date.now(),
quote: quote || undefined
});
appendMessage('assistant', msg, contact, false, quote);
lastShownMessage = msg;
}
if (lastShownMessage) {
contact.lastMessage = lastShownMessage.length > 20 ? lastShownMessage.substring(0, 20) + '...' : lastShownMessage;
}
requestSave();
refreshChatList();
}
} catch (err) {
hideTypingIndicator();
console.error('[可乐] 音乐分享AI回复失败:', err);
}
});
}
export function showExpandVoice() {
expandMode = 'voice';
const panel = document.getElementById('wechat-expand-input');
const title = document.getElementById('wechat-expand-title');
const body = document.getElementById('wechat-expand-body');
if (!panel || !title || !body) return;
title.textContent = '语音消息';
body.innerHTML = `
<div class="wechat-expand-hint">输入语音内容,系统会根据字数计算时长</div>
<textarea class="wechat-expand-textarea" id="wechat-expand-voice-text" placeholder="输入语音内容..."></textarea>
<div class="wechat-expand-preview">
<span class="wechat-expand-preview-label">预计时长:</span>
<span class="wechat-expand-preview-value" id="wechat-expand-voice-duration">0"</span>
</div>
`;
panel.classList.remove('hidden');
const textarea = document.getElementById('wechat-expand-voice-text');
textarea?.addEventListener('input', updateExpandVoiceDuration);
setTimeout(() => textarea?.focus(), 50);
}
// 显示照片描述输入面板
export function showExpandPhoto() {
expandMode = 'photo';
const panel = document.getElementById('wechat-expand-input');
const title = document.getElementById('wechat-expand-title');
const body = document.getElementById('wechat-expand-body');
if (!panel || !title || !body) return;
title.textContent = '发送照片';
body.innerHTML = `
<textarea class="wechat-expand-textarea" id="wechat-expand-photo-text" placeholder="描述照片内容..."></textarea>
`;
panel.classList.remove('hidden');
const textarea = document.getElementById('wechat-expand-photo-text');
setTimeout(() => textarea?.focus(), 50);
}
function updateExpandVoiceDuration() {
const textarea = document.getElementById('wechat-expand-voice-text');
const durationEl = document.getElementById('wechat-expand-voice-duration');
if (!textarea || !durationEl) return;
const content = textarea.value.trim();
const duration = content ? calculateVoiceDuration(content) : 0;
durationEl.textContent = duration + '"';
}
export function showExpandMulti() {
expandMode = 'multi';
expandMsgItems = [{ type: 'text', content: '' }];
const panel = document.getElementById('wechat-expand-input');
const title = document.getElementById('wechat-expand-title');
if (!panel || !title) return;
title.textContent = '混合消息';
renderExpandMsgList();
panel.classList.remove('hidden');
setTimeout(() => {
const firstInput = document.querySelector('.wechat-expand-msg-input');
firstInput?.focus();
}, 50);
}
// 获取消息类型的线条图标
function getTypeIcon(type) {
switch (type) {
case 'voice':
return `<svg viewBox="0 0 24 24" width="16" height="16"><path d="M12 2a3 3 0 00-3 3v6a3 3 0 006 0V5a3 3 0 00-3-3z" stroke="currentColor" stroke-width="1.5" fill="none"/><path d="M19 10v1a7 7 0 01-14 0v-1M12 18v3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none"/></svg>`;
case 'sticker':
return `<svg viewBox="0 0 24 24" width="16" height="16"><circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="1.5" fill="none"/><circle cx="9" cy="10" r="1" fill="currentColor"/><circle cx="15" cy="10" r="1" fill="currentColor"/><path d="M8 14c1 2 2.5 3 4 3s3-1 4-3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none"/></svg>`;
case 'photo':
return `<svg viewBox="0 0 24 24" width="16" height="16"><rect x="3" y="3" width="18" height="18" rx="2" stroke="currentColor" stroke-width="1.5" fill="none"/><circle cx="8.5" cy="8.5" r="1.5" stroke="currentColor" stroke-width="1.5" fill="none"/><path d="M21 15l-5-5L5 21" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/></svg>`;
default: // text
return `<svg viewBox="0 0 24 24" width="16" height="16"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z" stroke="currentColor" stroke-width="1.5" fill="none"/></svg>`;
}
}
// 获取消息类型标签
function getTypeLabel(type) {
switch (type) {
case 'voice': return '语音';
case 'sticker': return '表情';
case 'photo': return '照片';
default: return '文字';
}
}
function renderExpandMsgList() {
const body = document.getElementById('wechat-expand-body');
if (!body) return;
let html = '<div class=\"wechat-expand-msg-list\" id=\"wechat-expand-msg-list\">';
expandMsgItems.forEach((item, index) => {
const typeIcon = getTypeIcon(item.type);
const typeLabel = getTypeLabel(item.type);
html += `
<div class=\"wechat-expand-msg-item\" data-index=\"${index}\">
<span class=\"wechat-expand-msg-num\">${index + 1}</span>
<div class=\"wechat-expand-msg-type\" data-index=\"${index}\" title=\"点击切换类型\">
<span class=\"wechat-expand-type-icon\">${typeIcon}</span>
<span class=\"wechat-expand-type-label\">${typeLabel}</span>
</div>
`;
if (item.type === 'sticker') {
// 表情类型:显示选择按钮或已选的表情预览
if (item.content) {
html += `
<div class=\"wechat-expand-sticker-preview\" data-index=\"${index}\">
<img src=\"${escapeHtml(item.content)}\" alt=\"表情\" style=\"max-width: 50px; max-height: 50px; border-radius: 4px;\">
<button class=\"wechat-expand-sticker-change\" data-index=\"${index}\" title=\"更换表情\">换</button>
</div>
`;
} else {
html += `
<button class=\"wechat-expand-sticker-select\" data-index=\"${index}\">选择表情</button>
`;
}
} else if (item.type === 'photo') {
// 照片类型:输入图片描述
html += `
<input type=\"text\" class=\"wechat-expand-msg-input wechat-expand-photo-input\" data-index=\"${index}\" value=\"${escapeHtml(item.content)}\" placeholder=\"输入图片描述...\">
<span class=\"wechat-expand-photo-hint\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" stroke=\"currentColor\" stroke-width=\"1.5\" fill=\"none\"/><circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\" stroke=\"currentColor\" stroke-width=\"1.5\" fill=\"none\"/><path d=\"M21 15l-5-5L5 21\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" fill=\"none\"/></svg></span>
`;
} else if (item.type === 'voice') {
// 语音类型:输入框 + 时长显示
html += `
<input type=\"text\" class=\"wechat-expand-msg-input wechat-expand-voice-input\" data-index=\"${index}\" value=\"${escapeHtml(item.content)}\" placeholder=\"输入语音内容...\">
<span class=\"wechat-expand-voice-dur\">${item.content ? calculateVoiceDuration(item.content) + '\"' : '0\"'}</span>
`;
} else {
// 文字类型:普通输入框
html += `
<input type=\"text\" class=\"wechat-expand-msg-input\" data-index=\"${index}\" value=\"${escapeHtml(item.content)}\" placeholder=\"消息 ${index + 1}\">
`;
}
if (expandMsgItems.length > 1) {
html += `<button class=\"wechat-expand-msg-del\" data-index=\"${index}\">✕</button>`;
}
html += `</div>`;
});
html += '</div>';
html += '<button class=\"wechat-expand-add-btn\" id=\"wechat-expand-add-msg\">+ 添加消息</button>';
body.innerHTML = html;
// 绑定输入事件
document.querySelectorAll('.wechat-expand-msg-input').forEach(input => {
input.addEventListener('input', (e) => {
const index = parseInt(e.target.dataset.index);
expandMsgItems[index].content = e.target.value;
// 更新语音时长显示
if (expandMsgItems[index].type === 'voice') {
const durEl = e.target.parentElement.querySelector('.wechat-expand-voice-dur');
if (durEl) {
const duration = e.target.value.trim() ? calculateVoiceDuration(e.target.value) : 0;
durEl.textContent = duration + '\"';
}
}
});
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
addExpandMsgItem();
}
});
});
// 绑定类型切换事件
document.querySelectorAll('.wechat-expand-msg-type').forEach(typeBtn => {
typeBtn.addEventListener('click', (e) => {
const index = parseInt(typeBtn.dataset.index);
cycleMessageType(index);
});
});
// 绑定删除事件
document.querySelectorAll('.wechat-expand-msg-del').forEach(btn => {
btn.addEventListener('click', (e) => {
const index = parseInt(e.target.dataset.index);
expandMsgItems.splice(index, 1);
renderExpandMsgList();
});
});
// 绑定表情选择事件
document.querySelectorAll('.wechat-expand-sticker-select, .wechat-expand-sticker-change').forEach(btn => {
btn.addEventListener('click', (e) => {
const index = parseInt(btn.dataset.index);
openStickerPickerForMultiMsg(index);
});
});
document.getElementById('wechat-expand-add-msg')?.addEventListener('click', addExpandMsgItem);
}
// 循环切换消息类型
function cycleMessageType(index) {
const currentType = expandMsgItems[index].type;
let newType;
if (currentType === 'text') {
newType = 'voice';
} else if (currentType === 'voice') {
newType = 'sticker';
} else if (currentType === 'sticker') {
newType = 'photo';
} else {
newType = 'text';
}
expandMsgItems[index] = { type: newType, content: '' };
renderExpandMsgList();
}
function addExpandMsgItem() {
expandMsgItems.push({ type: 'text', content: '' });
renderExpandMsgList();
setTimeout(() => {
const inputs = document.querySelectorAll('.wechat-expand-msg-input');
const lastInput = inputs[inputs.length - 1];
lastInput?.focus();
}, 50);
}
// 打开表情选择器用于混合消息
function openStickerPickerForMultiMsg(index) {
pendingStickerIndex = index;
// 关闭展开面板,打开表情面板
const expandPanel = document.getElementById('wechat-expand-input');
const emojiPanel = document.getElementById('wechat-emoji-panel');
expandPanel?.classList.add('hidden');
emojiPanel?.classList.remove('hidden');
// 切换到贴纸标签
const stickerTab = document.querySelector('.wechat-emoji-tab[data-tab="sticker"]');
stickerTab?.click();
showToast('请选择表情', '😊');
}
// 为混合消息设置表情由emoji-panel调用
export function setStickerForMultiMsg(stickerUrl) {
if (pendingStickerIndex < 0 || pendingStickerIndex >= expandMsgItems.length) {
return false;
}
expandMsgItems[pendingStickerIndex].content = stickerUrl;
const savedIndex = pendingStickerIndex;
pendingStickerIndex = -1;
// 关闭表情面板,重新打开展开面板
const emojiPanel = document.getElementById('wechat-emoji-panel');
emojiPanel?.classList.add('hidden');
// 重新显示混合消息面板
expandMode = 'multi';
const panel = document.getElementById('wechat-expand-input');
const title = document.getElementById('wechat-expand-title');
if (panel && title) {
title.textContent = '混合消息';
renderExpandMsgList();
panel.classList.remove('hidden');
}
return true;
}
// 检查是否有待选表情
export function hasPendingStickerSelection() {
return pendingStickerIndex >= 0;
}
export function closeExpandPanel() {
const panel = document.getElementById('wechat-expand-input');
panel?.classList.add('hidden');
expandMode = null;
}
export async function sendExpandContent() {
const inGroup = isInGroupChat();
if (expandMode === 'voice') {
const textarea = document.getElementById('wechat-expand-voice-text');
const content = textarea?.value.trim();
if (!content) {
showToast('请输入语音内容', 'info');
return;
}
closeExpandPanel();
if (inGroup) {
sendGroupMessage(content, false, true);
} else {
sendMessage(content, false, true);
}
return;
}
if (expandMode === 'photo') {
const textarea = document.getElementById('wechat-expand-photo-text');
const content = textarea?.value.trim();
if (!content) {
showToast('请输入照片描述', 'info');
return;
}
closeExpandPanel();
if (inGroup) {
await sendGroupPhotoMessage(content);
} else {
await sendPhotoMessage(content);
}
return;
}
if (expandMode === 'multi') {
// 过滤有效消息(文字/语音需要有内容表情需要有URL
const validMessages = expandMsgItems.filter(m => {
if (m.type === 'sticker') {
return m.content && m.content.trim();
}
return m.content && m.content.trim();
});
if (validMessages.length === 0) {
showToast('请至少输入一条消息', 'info');
return;
}
closeExpandPanel();
// 使用批量发送函数一次性发完再调用AI
if (inGroup) {
await sendGroupBatchMessages(validMessages);
} else {
await sendBatchMessages(validMessages);
}
}
}
export function toggleFuncPanel() {
const panel = document.getElementById('wechat-func-panel');
const expandPanel = document.getElementById('wechat-expand-input');
const emojiPanel = document.getElementById('wechat-emoji-panel');
if (!panel || !expandPanel) return;
if (!expandPanel.classList.contains('hidden')) {
expandPanel.classList.add('hidden');
expandMode = null;
}
// 关闭表情面板
emojiPanel?.classList.add('hidden');
panel.classList.toggle('hidden');
}
export function hideFuncPanel() {
document.getElementById('wechat-func-panel')?.classList.add('hidden');
}
function setFuncPanelPage(pageIndex) {
funcPanelPage = pageIndex;
const pages = document.getElementById('wechat-func-pages');
const dots = document.querySelectorAll('.wechat-func-dot');
if (pages) pages.style.transform = `translateX(-${pageIndex * 100}%)`;
dots.forEach((dot, idx) => dot.classList.toggle('active', idx === pageIndex));
}
function handleFuncItemClick(func) {
switch (func) {
case 'voice':
hideFuncPanel();
showExpandVoice();
return;
case 'multi':
hideFuncPanel();
showExpandMulti();
return;
case 'photo':
hideFuncPanel();
showExpandPhoto();
return;
case 'voicecall':
hideFuncPanel();
startVoiceCall();
return;
case 'videocall':
hideFuncPanel();
startVideoCall();
return;
case 'music':
hideFuncPanel();
showMusicPanel();
return;
case 'redpacket':
hideFuncPanel();
if (isInGroupChat()) {
// 群聊红包 - 动态导入
import('./group-red-packet.js').then(m => m.showGroupRedPacketTypePage());
} else {
showRedPacketPage();
}
return;
case 'transfer':
hideFuncPanel();
if (isInGroupChat()) {
// 群聊转账 - 先选择成员
import('./group-red-packet.js').then(m => m.showGroupTransferSelectPage());
} else {
showTransferPage();
}
return;
case 'gift':
hideFuncPanel();
if (isInGroupChat()) {
showToast('群聊暂不支持送礼物', 'info');
return;
}
showGiftPage();
return;
case 'listen':
hideFuncPanel();
// 群聊不支持一起听
if (isInGroupChat()) {
showToast('群聊暂不支持一起听', 'info');
return;
}
showListenSearchPage();
return;
default:
showToast('该功能开发中...', 'info');
}
}
export function initFuncPanel() {
if (funcPanelInited) return;
const pages = document.getElementById('wechat-func-pages');
if (!pages) return;
funcPanelInited = true;
let startX = 0;
let currentX = 0;
let isDragging = false;
const handleStart = (e) => {
startX = e.type === 'touchstart' ? e.touches[0].clientX : e.clientX;
currentX = startX;
isDragging = true;
pages.style.transition = 'none';
};
const handleMove = (e) => {
if (!isDragging) return;
currentX = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX;
};
const handleEnd = () => {
if (!isDragging) return;
isDragging = false;
pages.style.transition = 'transform 0.3s ease';
const diff = startX - currentX;
if (Math.abs(diff) > 50) {
if (diff > 0 && funcPanelPage < 1) setFuncPanelPage(1);
else if (diff < 0 && funcPanelPage > 0) setFuncPanelPage(0);
}
};
pages.addEventListener('touchstart', handleStart, { passive: true });
pages.addEventListener('touchmove', handleMove, { passive: true });
pages.addEventListener('touchend', handleEnd);
pages.addEventListener('mousedown', (e) => {
handleStart(e);
e.preventDefault();
});
pages.addEventListener('mousemove', handleMove);
pages.addEventListener('mouseup', handleEnd);
pages.addEventListener('mouseleave', handleEnd);
document.querySelectorAll('.wechat-func-dot').forEach(dot => {
dot.addEventListener('click', () => {
const page = parseInt(dot.dataset.page);
setFuncPanelPage(page);
});
});
document.querySelectorAll('.wechat-func-item').forEach(item => {
item.addEventListener('click', () => {
handleFuncItemClick(item.dataset.func);
});
});
// 初始化音乐面板事件
initMusicEvents();
initMusicShareListener();
initListenTogether();
}