mirror of
https://github.com/Cola-Echo/Cola.git
synced 2026-06-06 07:45:50 +00:00
Add files via upload
This commit is contained in:
@@ -677,10 +677,6 @@ function handleFuncItemClick(func) {
|
|||||||
showTransferPage();
|
showTransferPage();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case 'time':
|
|
||||||
hideFuncPanel();
|
|
||||||
showTimePicker();
|
|
||||||
return;
|
|
||||||
case 'listen':
|
case 'listen':
|
||||||
hideFuncPanel();
|
hideFuncPanel();
|
||||||
// 群聊不支持一起听
|
// 群聊不支持一起听
|
||||||
@@ -758,194 +754,5 @@ export function initFuncPanel() {
|
|||||||
// 初始化音乐面板事件
|
// 初始化音乐面板事件
|
||||||
initMusicEvents();
|
initMusicEvents();
|
||||||
initMusicShareListener();
|
initMusicShareListener();
|
||||||
initTimePickerEvents();
|
|
||||||
initListenTogether();
|
initListenTogether();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============ 时间选择器相关 ============
|
|
||||||
|
|
||||||
// 存储选择的时间(null 表示使用当前时间)
|
|
||||||
let selectedTime = null;
|
|
||||||
let timePickerInited = false;
|
|
||||||
|
|
||||||
// 时间选择器当前选中的值
|
|
||||||
let pickerValues = {
|
|
||||||
year: new Date().getFullYear(),
|
|
||||||
month: new Date().getMonth() + 1,
|
|
||||||
day: new Date().getDate(),
|
|
||||||
hour: new Date().getHours(),
|
|
||||||
minute: new Date().getMinutes(),
|
|
||||||
second: new Date().getSeconds()
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取选择的时间(供 chat.js 使用)
|
|
||||||
export function getSelectedTime() {
|
|
||||||
return selectedTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清除选择的时间
|
|
||||||
export function clearSelectedTime() {
|
|
||||||
selectedTime = null;
|
|
||||||
updateTimeIndicator();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示时间选择器
|
|
||||||
function showTimePicker() {
|
|
||||||
const picker = document.getElementById('wechat-time-picker');
|
|
||||||
if (!picker) return;
|
|
||||||
|
|
||||||
// 初始化为当前时间
|
|
||||||
const now = new Date();
|
|
||||||
pickerValues = {
|
|
||||||
year: now.getFullYear(),
|
|
||||||
month: now.getMonth() + 1,
|
|
||||||
day: now.getDate(),
|
|
||||||
hour: now.getHours(),
|
|
||||||
minute: now.getMinutes(),
|
|
||||||
second: now.getSeconds()
|
|
||||||
};
|
|
||||||
|
|
||||||
renderTimePickerColumns();
|
|
||||||
updateTimePickerDisplay();
|
|
||||||
picker.classList.remove('hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 隐藏时间选择器
|
|
||||||
function hideTimePicker() {
|
|
||||||
const picker = document.getElementById('wechat-time-picker');
|
|
||||||
picker?.classList.add('hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 渲染时间选择器列
|
|
||||||
function renderTimePickerColumns() {
|
|
||||||
const currentYear = new Date().getFullYear();
|
|
||||||
|
|
||||||
// 年份:前后5年
|
|
||||||
renderPickerColumn('year', currentYear - 5, currentYear + 5, pickerValues.year, '年');
|
|
||||||
// 月份:1-12
|
|
||||||
renderPickerColumn('month', 1, 12, pickerValues.month, '月');
|
|
||||||
// 日期:根据年月动态计算
|
|
||||||
const daysInMonth = new Date(pickerValues.year, pickerValues.month, 0).getDate();
|
|
||||||
renderPickerColumn('day', 1, daysInMonth, pickerValues.day, '日');
|
|
||||||
// 小时:0-23
|
|
||||||
renderPickerColumn('hour', 0, 23, pickerValues.hour, '时');
|
|
||||||
// 分钟:0-59
|
|
||||||
renderPickerColumn('minute', 0, 59, pickerValues.minute, '分');
|
|
||||||
// 秒:0-59
|
|
||||||
renderPickerColumn('second', 0, 59, pickerValues.second, '秒');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 渲染单个列
|
|
||||||
function renderPickerColumn(type, min, max, selected, suffix) {
|
|
||||||
const container = document.getElementById(`wechat-time-picker-${type}`);
|
|
||||||
if (!container) return;
|
|
||||||
|
|
||||||
let html = '';
|
|
||||||
for (let i = min; i <= max; i++) {
|
|
||||||
const value = type === 'year' ? i : i.toString().padStart(2, '0');
|
|
||||||
const isSelected = i === selected;
|
|
||||||
html += `<div class="wechat-time-picker-item${isSelected ? ' selected' : ''}" data-value="${i}">${value}${suffix}</div>`;
|
|
||||||
}
|
|
||||||
container.innerHTML = html;
|
|
||||||
|
|
||||||
// 滚动到选中项
|
|
||||||
setTimeout(() => {
|
|
||||||
const selectedItem = container.querySelector('.selected');
|
|
||||||
if (selectedItem) {
|
|
||||||
container.scrollTop = selectedItem.offsetTop - container.offsetHeight / 2 + selectedItem.offsetHeight / 2;
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新显示的时间
|
|
||||||
function updateTimePickerDisplay() {
|
|
||||||
const display = document.getElementById('wechat-time-picker-display');
|
|
||||||
if (!display) return;
|
|
||||||
|
|
||||||
const { year, month, day, hour, minute, second } = pickerValues;
|
|
||||||
display.textContent = `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')} ${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:${second.toString().padStart(2, '0')}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新输入框旁的时间指示器
|
|
||||||
function updateTimeIndicator() {
|
|
||||||
let indicator = document.getElementById('wechat-time-indicator');
|
|
||||||
|
|
||||||
if (!selectedTime) {
|
|
||||||
indicator?.remove();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!indicator) {
|
|
||||||
const inputArea = document.querySelector('.wechat-chat-input-area');
|
|
||||||
if (!inputArea) return;
|
|
||||||
|
|
||||||
indicator = document.createElement('div');
|
|
||||||
indicator.id = 'wechat-time-indicator';
|
|
||||||
indicator.className = 'wechat-time-indicator';
|
|
||||||
inputArea.insertBefore(indicator, inputArea.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
const date = new Date(selectedTime);
|
|
||||||
const month = date.getMonth() + 1;
|
|
||||||
const day = date.getDate();
|
|
||||||
const hour = date.getHours().toString().padStart(2, '0');
|
|
||||||
const minute = date.getMinutes().toString().padStart(2, '0');
|
|
||||||
|
|
||||||
indicator.innerHTML = `
|
|
||||||
<span class="wechat-time-indicator-text">${month}月${day}日 ${hour}:${minute}</span>
|
|
||||||
<button class="wechat-time-indicator-clear" id="wechat-time-indicator-clear">✕</button>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// 绑定清除按钮
|
|
||||||
document.getElementById('wechat-time-indicator-clear')?.addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
clearSelectedTime();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化时间选择器事件
|
|
||||||
function initTimePickerEvents() {
|
|
||||||
if (timePickerInited) return;
|
|
||||||
timePickerInited = true;
|
|
||||||
|
|
||||||
// 监听列项点击
|
|
||||||
document.addEventListener('click', (e) => {
|
|
||||||
const item = e.target.closest('.wechat-time-picker-item');
|
|
||||||
if (!item) return;
|
|
||||||
|
|
||||||
const column = item.closest('.wechat-time-picker-column');
|
|
||||||
if (!column) return;
|
|
||||||
|
|
||||||
const type = column.dataset.type;
|
|
||||||
const value = parseInt(item.dataset.value);
|
|
||||||
|
|
||||||
// 更新选中值
|
|
||||||
pickerValues[type] = value;
|
|
||||||
|
|
||||||
// 更新选中样式
|
|
||||||
column.querySelectorAll('.wechat-time-picker-item').forEach(el => {
|
|
||||||
el.classList.toggle('selected', parseInt(el.dataset.value) === value);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 如果改变了年或月,需要重新渲染日期列
|
|
||||||
if (type === 'year' || type === 'month') {
|
|
||||||
const daysInMonth = new Date(pickerValues.year, pickerValues.month, 0).getDate();
|
|
||||||
if (pickerValues.day > daysInMonth) {
|
|
||||||
pickerValues.day = daysInMonth;
|
|
||||||
}
|
|
||||||
renderPickerColumn('day', 1, daysInMonth, pickerValues.day, '日');
|
|
||||||
}
|
|
||||||
|
|
||||||
updateTimePickerDisplay();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 确认按钮
|
|
||||||
document.getElementById('wechat-time-picker-confirm')?.addEventListener('click', () => {
|
|
||||||
const { year, month, day, hour, minute, second } = pickerValues;
|
|
||||||
selectedTime = new Date(year, month - 1, day, hour, minute, second).getTime();
|
|
||||||
|
|
||||||
hideTimePicker();
|
|
||||||
updateTimeIndicator();
|
|
||||||
showToast('已设置发送时间', '⏰');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
40
chat.js
40
chat.js
@@ -17,7 +17,6 @@ import { startVoiceCall } from './voice-call.js';
|
|||||||
import { startVideoCall } from './video-call.js';
|
import { startVideoCall } from './video-call.js';
|
||||||
import { showOpenRedPacket, generateRedPacketId } from './red-packet.js';
|
import { showOpenRedPacket, generateRedPacketId } from './red-packet.js';
|
||||||
import { showReceiveTransferPage, generateTransferId } from './transfer.js';
|
import { showReceiveTransferPage, generateTransferId } from './transfer.js';
|
||||||
import { getSelectedTime, clearSelectedTime } from './chat-func-panel.js';
|
|
||||||
|
|
||||||
// 当前聊天的联系人索引
|
// 当前聊天的联系人索引
|
||||||
export let currentChatIndex = -1;
|
export let currentChatIndex = -1;
|
||||||
@@ -1393,6 +1392,7 @@ export async function sendMessage(messageText, isMultipleMessages = false, isVoi
|
|||||||
|
|
||||||
const now = new Date();
|
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')}`;
|
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')}`;
|
||||||
|
const msgTimestamp = Date.now();
|
||||||
|
|
||||||
let messagesToSend = [];
|
let messagesToSend = [];
|
||||||
if (isMultipleMessages && Array.isArray(messageText)) {
|
if (isMultipleMessages && Array.isArray(messageText)) {
|
||||||
@@ -1413,8 +1413,6 @@ export async function sendMessage(messageText, isMultipleMessages = false, isVoi
|
|||||||
// 清除引用
|
// 清除引用
|
||||||
clearQuote();
|
clearQuote();
|
||||||
|
|
||||||
const msgTimestamp = Date.now();
|
|
||||||
|
|
||||||
for (let i = 0; i < messagesToSend.length; i++) {
|
for (let i = 0; i < messagesToSend.length; i++) {
|
||||||
const msg = messagesToSend[i];
|
const msg = messagesToSend[i];
|
||||||
// 只有第一条消息带引用
|
// 只有第一条消息带引用
|
||||||
@@ -1453,15 +1451,6 @@ export async function sendMessage(messageText, isMultipleMessages = false, isVoi
|
|||||||
? `[用户发送了语音消息,内容是:${messagesToSend.join('\n')}]`
|
? `[用户发送了语音消息,内容是:${messagesToSend.join('\n')}]`
|
||||||
: messagesToSend.join('\n');
|
: messagesToSend.join('\n');
|
||||||
|
|
||||||
// 如果有选择的时间,添加时间上下文
|
|
||||||
const selectedTime = getSelectedTime();
|
|
||||||
if (selectedTime) {
|
|
||||||
const timeDate = new Date(selectedTime);
|
|
||||||
const timeStr = `${timeDate.getFullYear()}年${timeDate.getMonth() + 1}月${timeDate.getDate()}日 ${timeDate.getHours().toString().padStart(2, '0')}:${timeDate.getMinutes().toString().padStart(2, '0')}`;
|
|
||||||
combinedMessage = `[当前时间:${timeStr}]\n${combinedMessage}`;
|
|
||||||
clearSelectedTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果有引用,添加引用上下文
|
// 如果有引用,添加引用上下文
|
||||||
if (quote) {
|
if (quote) {
|
||||||
let quoteDesc;
|
let quoteDesc;
|
||||||
@@ -1981,15 +1970,6 @@ export async function sendStickerMessage(stickerUrl, description = '') {
|
|||||||
? `[用户发送了一个表情包:${description}]`
|
? `[用户发送了一个表情包:${description}]`
|
||||||
: '[用户发送了一个表情包]';
|
: '[用户发送了一个表情包]';
|
||||||
|
|
||||||
// 如果有选择的时间,添加时间上下文
|
|
||||||
const selectedTime = getSelectedTime();
|
|
||||||
if (selectedTime) {
|
|
||||||
const timeDate = new Date(selectedTime);
|
|
||||||
const timeStr = `${timeDate.getFullYear()}年${timeDate.getMonth() + 1}月${timeDate.getDate()}日 ${timeDate.getHours().toString().padStart(2, '0')}:${timeDate.getMinutes().toString().padStart(2, '0')}`;
|
|
||||||
aiPrompt = `[当前时间:${timeStr}]\n${aiPrompt}`;
|
|
||||||
clearSelectedTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
const aiResponse = await callAI(contact, aiPrompt);
|
const aiResponse = await callAI(contact, aiPrompt);
|
||||||
|
|
||||||
// 只有用户还在当前聊天时才隐藏打字指示器
|
// 只有用户还在当前聊天时才隐藏打字指示器
|
||||||
@@ -2371,15 +2351,6 @@ export async function sendPhotoMessage(description) {
|
|||||||
const { callAI } = await import('./ai.js');
|
const { callAI } = await import('./ai.js');
|
||||||
let aiPrompt = `[用户发送了一张照片,图片描述:${polishedDescription}]`;
|
let aiPrompt = `[用户发送了一张照片,图片描述:${polishedDescription}]`;
|
||||||
|
|
||||||
// 如果有选择的时间,添加时间上下文
|
|
||||||
const selectedTime = getSelectedTime();
|
|
||||||
if (selectedTime) {
|
|
||||||
const timeDate = new Date(selectedTime);
|
|
||||||
const timeStr = `${timeDate.getFullYear()}年${timeDate.getMonth() + 1}月${timeDate.getDate()}日 ${timeDate.getHours().toString().padStart(2, '0')}:${timeDate.getMinutes().toString().padStart(2, '0')}`;
|
|
||||||
aiPrompt = `[当前时间:${timeStr}]\n${aiPrompt}`;
|
|
||||||
clearSelectedTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
const aiResponse = await callAI(contact, aiPrompt);
|
const aiResponse = await callAI(contact, aiPrompt);
|
||||||
|
|
||||||
// 只有用户还在当前聊天时才隐藏打字指示器
|
// 只有用户还在当前聊天时才隐藏打字指示器
|
||||||
@@ -2861,15 +2832,6 @@ export async function sendBatchMessages(messages) {
|
|||||||
const { callAI } = await import('./ai.js');
|
const { callAI } = await import('./ai.js');
|
||||||
let combinedPrompt = promptParts.join('\n');
|
let combinedPrompt = promptParts.join('\n');
|
||||||
|
|
||||||
// 如果有选择的时间,添加时间上下文
|
|
||||||
const selectedTime = getSelectedTime();
|
|
||||||
if (selectedTime) {
|
|
||||||
const timeDate = new Date(selectedTime);
|
|
||||||
const timeStr = `${timeDate.getFullYear()}年${timeDate.getMonth() + 1}月${timeDate.getDate()}日 ${timeDate.getHours().toString().padStart(2, '0')}:${timeDate.getMinutes().toString().padStart(2, '0')}`;
|
|
||||||
combinedPrompt = `[当前时间:${timeStr}]\n${combinedPrompt}`;
|
|
||||||
clearSelectedTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
const aiResponse = await callAI(contact, combinedPrompt);
|
const aiResponse = await callAI(contact, combinedPrompt);
|
||||||
|
|
||||||
// 只有用户还在当前聊天时才隐藏打字指示器
|
// 只有用户还在当前聊天时才隐藏打字指示器
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { escapeHtml, sleep, formatMessageTime, calculateVoiceDuration, bindImage
|
|||||||
import { getUserAvatarHTML, refreshChatList, getUserPersonaFromST } from './ui.js';
|
import { getUserAvatarHTML, refreshChatList, getUserPersonaFromST } from './ui.js';
|
||||||
import { getSTChatContext, HAKIMI_HEADER } from './ai.js';
|
import { getSTChatContext, HAKIMI_HEADER } from './ai.js';
|
||||||
import { playMusic as kugouPlayMusic } from './music.js';
|
import { playMusic as kugouPlayMusic } from './music.js';
|
||||||
|
import { showMessageMenu } from './message-menu.js';
|
||||||
|
|
||||||
// 当前群聊的索引
|
// 当前群聊的索引
|
||||||
export let currentGroupChatIndex = -1;
|
export let currentGroupChatIndex = -1;
|
||||||
@@ -813,25 +814,29 @@ function renderGroupChatHistory(groupChat, members, chatHistory) {
|
|||||||
|
|
||||||
// 生成群聊静态语音气泡
|
// 生成群聊静态语音气泡
|
||||||
function generateGroupVoiceBubbleStatic(content, isSelf) {
|
function generateGroupVoiceBubbleStatic(content, isSelf) {
|
||||||
const seconds = calculateVoiceDuration(content);
|
const safeContent = (content || '').toString();
|
||||||
const width = Math.min(50 + seconds * 3, 180);
|
const seconds = calculateVoiceDuration(safeContent);
|
||||||
|
const width = Math.min(60 + seconds * 4, 200);
|
||||||
const voiceId = 'voice_' + Math.random().toString(36).substring(2, 9);
|
const voiceId = 'voice_' + Math.random().toString(36).substring(2, 9);
|
||||||
// 用户消息波形朝右,角色消息波形朝左
|
|
||||||
const wavesSvg = isSelf
|
|
||||||
? `<svg viewBox="0 0 24 24" width="20" height="20"><path d="M3 12h2v4H3zM7 8h2v8H7zm4 4h2v6h-2zm4-6h2v10h-2z" fill="currentColor"/></svg>`
|
|
||||||
: `<svg viewBox="0 0 24 24" width="20" height="20"><path d="M19 12h2v4h-2zm-4-4h2v8h-2zm-4 4h2v6h-2zm-4-6h2v10H7z" fill="currentColor"/></svg>`;
|
|
||||||
|
|
||||||
// 用户消息:波形在左,秒数在右
|
// WiFi信号样式的三条弧线图标(与单聊保持一致)
|
||||||
// 角色消息:秒数在左,波形在右
|
const wavesSvg = `<svg class="wechat-voice-waves-icon" viewBox="0 0 24 24" width="18" height="18">
|
||||||
|
<circle class="wechat-voice-arc arc1" cx="5" cy="12" r="2" fill="currentColor"/>
|
||||||
|
<path class="wechat-voice-arc arc2" d="M10 8 A 5 5 0 0 1 10 16" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
|
||||||
|
<path class="wechat-voice-arc arc3" d="M15 4 A 10 10 0 0 1 15 20" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
|
||||||
|
</svg>`;
|
||||||
|
|
||||||
|
// 用户消息:时长在左,波形在右
|
||||||
|
// 角色消息:波形在左,时长在右
|
||||||
const bubbleInner = isSelf
|
const bubbleInner = isSelf
|
||||||
? `<span class="wechat-voice-waves">${wavesSvg}</span><span class="wechat-voice-duration">${seconds}</span>`
|
? `<span class="wechat-voice-duration">${seconds}"</span><span class="wechat-voice-waves">${wavesSvg}</span>`
|
||||||
: `<span class="wechat-voice-duration">${seconds}</span><span class="wechat-voice-waves">${wavesSvg}</span>`;
|
: `<span class="wechat-voice-waves">${wavesSvg}</span><span class="wechat-voice-duration">${seconds}"</span>`;
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="wechat-voice-bubble ${isSelf ? 'self' : ''}" style="width: ${width}px" data-voice-id="${voiceId}">
|
<div class="wechat-voice-bubble ${isSelf ? 'self' : ''}" style="width: ${width}px" data-voice-id="${voiceId}" data-voice-content="${escapeHtml(safeContent)}">
|
||||||
${bubbleInner}
|
${bubbleInner}
|
||||||
</div>
|
</div>
|
||||||
<div class="wechat-voice-text hidden" id="${voiceId}">${escapeHtml(content)}</div>
|
<div class="wechat-voice-text hidden" id="${voiceId}">${escapeHtml(safeContent)}</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -866,17 +871,43 @@ function generateGroupMusicCardStatic(musicInfo) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 绑定群聊语音气泡点击事件
|
// 绑定群聊语音气泡点击事件(播放动画 + 显示上方菜单,与单聊保持一致)
|
||||||
function bindGroupVoiceBubbleEvents(container) {
|
function bindGroupVoiceBubbleEvents(container) {
|
||||||
const voiceBubbles = container.querySelectorAll('.wechat-voice-bubble:not([data-bound])');
|
const voiceBubbles = container.querySelectorAll('.wechat-voice-bubble:not([data-bound])');
|
||||||
voiceBubbles.forEach(bubble => {
|
voiceBubbles.forEach(bubble => {
|
||||||
bubble.setAttribute('data-bound', 'true');
|
bubble.setAttribute('data-bound', 'true');
|
||||||
bubble.addEventListener('click', () => {
|
|
||||||
const voiceId = bubble.dataset.voiceId;
|
// 获取父消息元素
|
||||||
const textEl = document.getElementById(voiceId);
|
const messageEl = bubble.closest('.wechat-message');
|
||||||
if (textEl) {
|
|
||||||
textEl.classList.toggle('hidden');
|
// 计算消息索引
|
||||||
|
const allMessages = Array.from(container.querySelectorAll('.wechat-message'));
|
||||||
|
const msgIndex = allMessages.indexOf(messageEl);
|
||||||
|
|
||||||
|
// 点击事件:播放动画 + 显示上方菜单
|
||||||
|
bubble.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// 切换播放状态
|
||||||
|
const isPlaying = bubble.classList.contains('playing');
|
||||||
|
if (isPlaying) {
|
||||||
|
bubble.classList.remove('playing');
|
||||||
|
} else {
|
||||||
|
// 停止其他正在播放的语音
|
||||||
|
document.querySelectorAll('.wechat-voice-bubble.playing').forEach(b => {
|
||||||
|
b.classList.remove('playing');
|
||||||
|
});
|
||||||
|
bubble.classList.add('playing');
|
||||||
|
|
||||||
|
// 模拟播放时间后停止
|
||||||
|
const duration = parseInt(bubble.querySelector('.wechat-voice-duration')?.textContent) || 3;
|
||||||
|
setTimeout(() => {
|
||||||
|
bubble.classList.remove('playing');
|
||||||
|
}, duration * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 显示上方菜单
|
||||||
|
showMessageMenu(bubble, msgIndex, e);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
4
main.js
4
main.js
@@ -1634,8 +1634,8 @@ function init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof jQuery === 'function') {
|
if (typeof jQuery === 'function') {
|
||||||
jQuery(() => init());
|
jQuery(() => setTimeout(init, 500));
|
||||||
} else {
|
} else {
|
||||||
document.addEventListener('DOMContentLoaded', init, { once: true });
|
document.addEventListener('DOMContentLoaded', () => setTimeout(init, 500), { once: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -205,28 +205,6 @@ export function generatePhoneHTML() {
|
|||||||
<input type="file" id="wechat-chat-bg-file" accept="image/*" style="display: none;">
|
<input type="file" id="wechat-chat-bg-file" accept="image/*" style="display: none;">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 图片裁剪弹窗 -->
|
|
||||||
<div id="wechat-cropper-modal" class="wechat-modal hidden">
|
|
||||||
<div class="wechat-modal-content wechat-modal-cropper" style="position: relative; max-width: 350px; max-height: 90vh; overflow: hidden;">
|
|
||||||
<button class="wechat-modal-close-x" id="wechat-cropper-cancel">×</button>
|
|
||||||
<div class="wechat-modal-title">裁剪图片</div>
|
|
||||||
<div class="wechat-cropper-container" id="wechat-cropper-container">
|
|
||||||
<canvas id="wechat-cropper-canvas"></canvas>
|
|
||||||
<div class="wechat-cropper-overlay" id="wechat-cropper-overlay">
|
|
||||||
<div class="wechat-cropper-box" id="wechat-cropper-box">
|
|
||||||
<div class="wechat-cropper-handle nw"></div>
|
|
||||||
<div class="wechat-cropper-handle ne"></div>
|
|
||||||
<div class="wechat-cropper-handle sw"></div>
|
|
||||||
<div class="wechat-cropper-handle se"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="wechat-cropper-hint" style="font-size: 11px; color: var(--wechat-text-secondary); text-align: center; margin: 8px 0;">拖动选择区域,拖动角落调整大小</div>
|
|
||||||
<div class="wechat-modal-actions">
|
|
||||||
<button class="wechat-btn wechat-btn-primary" id="wechat-cropper-confirm">确认裁剪</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="wechat-chat">
|
<div class="wechat-chat">
|
||||||
<div class="wechat-chat-messages" id="wechat-chat-messages"></div>
|
<div class="wechat-chat-messages" id="wechat-chat-messages"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -248,7 +226,6 @@ export function generatePhoneHTML() {
|
|||||||
<div class="wechat-func-page" data-page="1">
|
<div class="wechat-func-page" data-page="1">
|
||||||
<div class="wechat-func-grid">
|
<div class="wechat-func-grid">
|
||||||
<div class="wechat-func-item" data-func="voice"><div class="wechat-func-icon"><svg viewBox="0 0 24 24"><path d="M12 1a4 4 0 00-4 4v7a4 4 0 008 0V5a4 4 0 00-4-4z" stroke="currentColor" stroke-width="1.5" fill="none"/><path d="M19 10v2a7 7 0 01-14 0v-2M12 19v4M8 23h8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none"/></svg></div><span>语音输入</span></div>
|
<div class="wechat-func-item" data-func="voice"><div class="wechat-func-icon"><svg viewBox="0 0 24 24"><path d="M12 1a4 4 0 00-4 4v7a4 4 0 008 0V5a4 4 0 00-4-4z" stroke="currentColor" stroke-width="1.5" fill="none"/><path d="M19 10v2a7 7 0 01-14 0v-2M12 19v4M8 23h8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none"/></svg></div><span>语音输入</span></div>
|
||||||
<div class="wechat-func-item" data-func="time"><div class="wechat-func-icon"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="1.5" fill="none"/><path d="M12 6v6l4 2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/></svg></div><span>时间</span></div>
|
|
||||||
<div class="wechat-func-item" data-func="listen"><div class="wechat-func-icon"><svg viewBox="0 0 24 24"><path d="M3 18v-6a9 9 0 0118 0v6" stroke="currentColor" stroke-width="1.5" fill="none"/><path d="M21 19a2 2 0 01-2 2h-1a2 2 0 01-2-2v-3a2 2 0 012-2h3v5z" stroke="currentColor" stroke-width="1.5" fill="none"/><path d="M3 19a2 2 0 002 2h1a2 2 0 002-2v-3a2 2 0 00-2-2H3v5z" stroke="currentColor" stroke-width="1.5" fill="none"/></svg></div><span>一起听</span></div>
|
<div class="wechat-func-item" data-func="listen"><div class="wechat-func-icon"><svg viewBox="0 0 24 24"><path d="M3 18v-6a9 9 0 0118 0v6" stroke="currentColor" stroke-width="1.5" fill="none"/><path d="M21 19a2 2 0 01-2 2h-1a2 2 0 01-2-2v-3a2 2 0 012-2h3v5z" stroke="currentColor" stroke-width="1.5" fill="none"/><path d="M3 19a2 2 0 002 2h1a2 2 0 002-2v-3a2 2 0 00-2-2H3v5z" stroke="currentColor" stroke-width="1.5" fill="none"/></svg></div><span>一起听</span></div>
|
||||||
<div class="wechat-func-item" data-func="music"><div class="wechat-func-icon"><svg viewBox="0 0 24 24"><path d="M9 18V5l12-2v13" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/><circle cx="6" cy="18" r="3" stroke="currentColor" stroke-width="1.5" fill="none"/><circle cx="18" cy="16" r="3" stroke="currentColor" stroke-width="1.5" fill="none"/></svg></div><span>音乐</span></div>
|
<div class="wechat-func-item" data-func="music"><div class="wechat-func-icon"><svg viewBox="0 0 24 24"><path d="M9 18V5l12-2v13" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/><circle cx="6" cy="18" r="3" stroke="currentColor" stroke-width="1.5" fill="none"/><circle cx="18" cy="16" r="3" stroke="currentColor" stroke-width="1.5" fill="none"/></svg></div><span>音乐</span></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -270,36 +247,6 @@ export function generatePhoneHTML() {
|
|||||||
<button class="wechat-btn wechat-expand-send" id="wechat-expand-send">发送</button>
|
<button class="wechat-btn wechat-expand-send" id="wechat-expand-send">发送</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 时间选择器面板 -->
|
|
||||||
<div class="wechat-time-picker hidden" id="wechat-time-picker">
|
|
||||||
<div class="wechat-time-picker-header">
|
|
||||||
<span class="wechat-time-picker-title">发送时间</span>
|
|
||||||
</div>
|
|
||||||
<div class="wechat-time-picker-display" id="wechat-time-picker-display">2025-12-22 21:33:19</div>
|
|
||||||
<div class="wechat-time-picker-columns">
|
|
||||||
<div class="wechat-time-picker-column" data-type="year">
|
|
||||||
<div class="wechat-time-picker-items" id="wechat-time-picker-year"></div>
|
|
||||||
</div>
|
|
||||||
<div class="wechat-time-picker-column" data-type="month">
|
|
||||||
<div class="wechat-time-picker-items" id="wechat-time-picker-month"></div>
|
|
||||||
</div>
|
|
||||||
<div class="wechat-time-picker-column" data-type="day">
|
|
||||||
<div class="wechat-time-picker-items" id="wechat-time-picker-day"></div>
|
|
||||||
</div>
|
|
||||||
<div class="wechat-time-picker-column" data-type="hour">
|
|
||||||
<div class="wechat-time-picker-items" id="wechat-time-picker-hour"></div>
|
|
||||||
</div>
|
|
||||||
<div class="wechat-time-picker-column" data-type="minute">
|
|
||||||
<div class="wechat-time-picker-items" id="wechat-time-picker-minute"></div>
|
|
||||||
</div>
|
|
||||||
<div class="wechat-time-picker-column" data-type="second">
|
|
||||||
<div class="wechat-time-picker-items" id="wechat-time-picker-second"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="wechat-time-picker-footer">
|
|
||||||
<button class="wechat-time-picker-confirm" id="wechat-time-picker-confirm">完成</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 表情面板 -->
|
<!-- 表情面板 -->
|
||||||
<div class="wechat-emoji-panel hidden" id="wechat-emoji-panel">
|
<div class="wechat-emoji-panel hidden" id="wechat-emoji-panel">
|
||||||
<div class="wechat-emoji-tabs">
|
<div class="wechat-emoji-tabs">
|
||||||
@@ -342,6 +289,29 @@ export function generatePhoneHTML() {
|
|||||||
${generateTransferPageHTML()}
|
${generateTransferPageHTML()}
|
||||||
${generateReceiveTransferPageHTML()}
|
${generateReceiveTransferPageHTML()}
|
||||||
${generateTransferRefundConfirmHTML()}
|
${generateTransferRefundConfirmHTML()}
|
||||||
|
|
||||||
|
<!-- 图片裁剪弹窗(全局) -->
|
||||||
|
<div id="wechat-cropper-modal" class="wechat-modal hidden">
|
||||||
|
<div class="wechat-modal-content wechat-modal-cropper" style="position: relative; max-width: 350px; max-height: 90vh; overflow: hidden;">
|
||||||
|
<button class="wechat-modal-close-x" id="wechat-cropper-cancel">×</button>
|
||||||
|
<div class="wechat-modal-title">裁剪图片</div>
|
||||||
|
<div class="wechat-cropper-container" id="wechat-cropper-container">
|
||||||
|
<canvas id="wechat-cropper-canvas"></canvas>
|
||||||
|
<div class="wechat-cropper-overlay" id="wechat-cropper-overlay">
|
||||||
|
<div class="wechat-cropper-box" id="wechat-cropper-box">
|
||||||
|
<div class="wechat-cropper-handle nw"></div>
|
||||||
|
<div class="wechat-cropper-handle ne"></div>
|
||||||
|
<div class="wechat-cropper-handle sw"></div>
|
||||||
|
<div class="wechat-cropper-handle se"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="wechat-cropper-hint" style="font-size: 11px; color: var(--wechat-text-secondary); text-align: center; margin: 8px 0;">拖动选择区域,拖动角落调整大小</div>
|
||||||
|
<div class="wechat-modal-actions">
|
||||||
|
<button class="wechat-btn wechat-btn-primary" id="wechat-cropper-confirm">确认裁剪</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 隐藏的文件输入 -->
|
<!-- 隐藏的文件输入 -->
|
||||||
|
|||||||
159
style.css
159
style.css
@@ -8767,165 +8767,6 @@
|
|||||||
color: rgba(255,255,255,0.7);
|
color: rgba(255,255,255,0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== 时间选择器 ===== */
|
|
||||||
.wechat-time-picker {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background: var(--wechat-white);
|
|
||||||
border-top: 1px solid var(--wechat-border);
|
|
||||||
z-index: 100;
|
|
||||||
animation: slideUp 0.25s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes slideUp {
|
|
||||||
from {
|
|
||||||
transform: translateY(100%);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.wechat-time-picker.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wechat-time-picker-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 12px 16px;
|
|
||||||
border-bottom: 1px solid var(--wechat-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wechat-time-picker-title {
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--wechat-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wechat-time-picker-confirm {
|
|
||||||
background: var(--wechat-green);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 6px 16px;
|
|
||||||
font-size: 14px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: opacity 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wechat-time-picker-confirm:active {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wechat-time-picker-display {
|
|
||||||
text-align: center;
|
|
||||||
padding: 10px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--wechat-green);
|
|
||||||
font-weight: 500;
|
|
||||||
background: var(--wechat-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wechat-time-picker-columns {
|
|
||||||
display: flex;
|
|
||||||
height: 180px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wechat-time-picker-column {
|
|
||||||
flex: 1;
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: auto;
|
|
||||||
scroll-snap-type: y mandatory;
|
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 隐藏滚动条 */
|
|
||||||
.wechat-time-picker-column::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wechat-time-picker-column {
|
|
||||||
scrollbar-width: none;
|
|
||||||
-ms-overflow-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wechat-time-picker-item {
|
|
||||||
height: 36px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--wechat-text-secondary);
|
|
||||||
cursor: pointer;
|
|
||||||
scroll-snap-align: center;
|
|
||||||
transition: all 0.15s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wechat-time-picker-item:active {
|
|
||||||
background: var(--wechat-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wechat-time-picker-item.selected {
|
|
||||||
color: var(--wechat-text);
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 时间指示器(输入框上方) */
|
|
||||||
.wechat-time-indicator {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 6px 12px;
|
|
||||||
background: rgba(7, 193, 96, 0.1);
|
|
||||||
border-bottom: 1px solid var(--wechat-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wechat-time-indicator-text {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--wechat-green);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wechat-time-indicator-clear {
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
border: none;
|
|
||||||
background: var(--wechat-text-secondary);
|
|
||||||
color: white;
|
|
||||||
border-radius: 50%;
|
|
||||||
font-size: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
opacity: 0.6;
|
|
||||||
transition: opacity 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wechat-time-indicator-clear:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 暗色模式时间选择器 */
|
|
||||||
.wechat-dark .wechat-time-picker {
|
|
||||||
background: var(--wechat-white);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wechat-dark .wechat-time-picker-display {
|
|
||||||
background: var(--wechat-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wechat-dark .wechat-time-indicator {
|
|
||||||
background: rgba(62, 181, 117, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ========== 一起听功能样式 ========== */
|
/* ========== 一起听功能样式 ========== */
|
||||||
|
|
||||||
/* 搜索页面 */
|
/* 搜索页面 */
|
||||||
|
|||||||
Reference in New Issue
Block a user