新增聊天室发送图片功能
This commit is contained in:
@@ -151,6 +151,80 @@
|
||||
|
||||
window.showVipPresenceBanner = showVipPresenceBanner;
|
||||
|
||||
/**
|
||||
* 判断图片消息是否已经超过前端允许展示的保留期。
|
||||
*/
|
||||
function isExpiredChatImageMessage(msg) {
|
||||
if (!msg) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (msg.message_type === 'expired_image') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (msg.message_type !== 'image') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!msg.image_url || !msg.image_thumb_url) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const retentionDays = parseInt(window.chatContext?.chatImageRetentionDays || 3, 10);
|
||||
const sentAtText = String(msg.sent_at || '').replace(' ', 'T');
|
||||
const sentAt = sentAtText ? new Date(sentAtText) : null;
|
||||
|
||||
if (!sentAt || Number.isNaN(sentAt.getTime())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Date.now() >= sentAt.getTime() + retentionDays * 24 * 60 * 60 * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建普通聊天消息的正文区域,支持缩略图与过期占位渲染。
|
||||
*/
|
||||
function buildChatMessageContent(msg, fontColor) {
|
||||
const rawContent = msg.content || '';
|
||||
|
||||
if (msg.message_type === 'image' && !isExpiredChatImageMessage(msg)) {
|
||||
const fullUrl = escapeHtml(msg.image_url || '');
|
||||
const thumbUrl = escapeHtml(msg.image_thumb_url || '');
|
||||
const imageName = escapeHtml(msg.image_original_name || '聊天图片');
|
||||
const captionHtml = rawContent ?
|
||||
`<span style="display:inline-block; max-width:220px; color:${fontColor}; line-height:1.55;">${rawContent}</span>` :
|
||||
'';
|
||||
|
||||
return `
|
||||
<span style="display:inline-flex; align-items:flex-start; gap:6px; vertical-align:middle;">
|
||||
<a href="${fullUrl}" data-full="${fullUrl}" data-alt="${imageName}"
|
||||
onclick="openChatImageLightbox(this.dataset.full, this.dataset.alt); return false;"
|
||||
style="display:inline-block; border:1px solid rgba(15,23,42,.14); border-radius:10px; overflow:hidden; background:#f8fafc; box-shadow:0 2px 10px rgba(15,23,42,.10);">
|
||||
<img src="${thumbUrl}" alt="${imageName}"
|
||||
style="display:block; max-width:96px; max-height:96px; object-fit:cover; cursor:zoom-in;">
|
||||
</a>
|
||||
${captionHtml}
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
|
||||
if (msg.message_type === 'expired_image' || isExpiredChatImageMessage(msg)) {
|
||||
const captionHtml = rawContent ?
|
||||
`<span style="display:inline-block; color:${fontColor}; line-height:1.55;">${rawContent}</span>` :
|
||||
'';
|
||||
|
||||
return `
|
||||
<span style="display:inline-flex; align-items:center; gap:6px; vertical-align:middle;">
|
||||
<span style="display:inline-flex; align-items:center; padding:4px 8px; border:1px dashed #94a3b8; border-radius:999px; background:#f8fafc; color:#64748b; font-size:12px;">🖼️ 图片已过期</span>
|
||||
${captionHtml}
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
|
||||
return rawContent;
|
||||
}
|
||||
|
||||
// ── Tab 切换 ──────────────────────────────────────
|
||||
let _roomsRefreshTimer = null;
|
||||
|
||||
@@ -606,6 +680,7 @@
|
||||
}
|
||||
const headImg =
|
||||
`<img src="${headImgSrc}" style="display:inline;width:16px;height:16px;vertical-align:middle;margin-right:2px;mix-blend-mode: multiply;" onerror="this.src='/images/headface/1.gif'">`;
|
||||
const messageBodyHtml = buildChatMessageContent(msg, fontColor);
|
||||
|
||||
let html = '';
|
||||
|
||||
@@ -726,7 +801,7 @@
|
||||
buildActionStr(msg.action, fromHtml, toHtml, '悄悄说') :
|
||||
`${fromHtml}对${toHtml}悄悄说:`;
|
||||
html =
|
||||
`${headImg}<span class="msg-secret">${verbStr}</span><span class="msg-content" style="color: ${fontColor}; font-style: italic;">${msg.content}</span>`;
|
||||
`${headImg}<span class="msg-secret">${verbStr}</span><span class="msg-content" style="color: ${fontColor}; font-style: italic;">${messageBodyHtml}</span>`;
|
||||
}
|
||||
} else if (msg.to_user && msg.to_user !== '大家') {
|
||||
// 对特定对象说话
|
||||
@@ -735,14 +810,14 @@
|
||||
const verbStr = msg.action ?
|
||||
buildActionStr(msg.action, fromHtml, toHtml) :
|
||||
`${fromHtml}对${toHtml}说:`;
|
||||
html = `${headImg}${verbStr}<span class="msg-content" style="color: ${fontColor}">${msg.content}</span>`;
|
||||
html = `${headImg}${verbStr}<span class="msg-content" style="color: ${fontColor}">${messageBodyHtml}</span>`;
|
||||
} else {
|
||||
// 对大家说话
|
||||
const fromHtml = clickableUser(msg.from_user, '#000099');
|
||||
const verbStr = msg.action ?
|
||||
buildActionStr(msg.action, fromHtml, '大家') :
|
||||
`${fromHtml}对大家说:`;
|
||||
html = `${headImg}${verbStr}<span class="msg-content" style="color: ${fontColor}">${msg.content}</span>`;
|
||||
html = `${headImg}${verbStr}<span class="msg-content" style="color: ${fontColor}">${messageBodyHtml}</span>`;
|
||||
}
|
||||
|
||||
if (!timeStrOverride) {
|
||||
@@ -1251,6 +1326,99 @@
|
||||
let _imeComposing = false;
|
||||
const _contentInput = document.getElementById('content');
|
||||
|
||||
/**
|
||||
* 更新底部图片选择状态提示。
|
||||
*/
|
||||
function updateChatImageSelectionLabel(filename = '') {
|
||||
const nameEl = document.getElementById('chat-image-name');
|
||||
if (!nameEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
nameEl.textContent = filename || '未选择图片';
|
||||
nameEl.style.color = filename ? '#0f766e' : '#64748b';
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理聊天图片选择后的前端状态展示。
|
||||
*/
|
||||
function handleChatImageSelected(input) {
|
||||
const file = input?.files?.[0] ?? null;
|
||||
if (!file) {
|
||||
updateChatImageSelectionLabel('');
|
||||
return;
|
||||
}
|
||||
|
||||
updateChatImageSelectionLabel(file.name);
|
||||
|
||||
// 用户选择图片后,立即触发自动发送
|
||||
sendMessage(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理当前选中的聊天图片。
|
||||
*/
|
||||
function clearSelectedChatImage(resetInput = false) {
|
||||
const imageInput = document.getElementById('chat_image');
|
||||
|
||||
if (resetInput && imageInput) {
|
||||
imageInput.value = '';
|
||||
}
|
||||
|
||||
updateChatImageSelectionLabel('');
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开聊天图片大图预览层。
|
||||
*/
|
||||
function openChatImageLightbox(imageUrl, imageName = '聊天图片') {
|
||||
const lightbox = document.getElementById('chat-image-lightbox');
|
||||
const imageEl = document.getElementById('chat-image-lightbox-img');
|
||||
const nameEl = document.getElementById('chat-image-lightbox-name');
|
||||
|
||||
if (!lightbox || !imageEl || !imageUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
imageEl.src = imageUrl;
|
||||
imageEl.alt = imageName;
|
||||
|
||||
if (nameEl) {
|
||||
nameEl.textContent = imageName;
|
||||
}
|
||||
|
||||
lightbox.style.display = 'block';
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭聊天图片大图预览层。
|
||||
*/
|
||||
function closeChatImageLightbox(event = null) {
|
||||
// 如果是点击事件,且点击的目标不是背景或关闭按钮(比如点击了图片本身且没有阻止冒泡),则不关闭
|
||||
// 已经在 HTML 中对 img 做了 stopPropagation,此处 event.target !== event.currentTarget 仍是安全的
|
||||
if (event && event.target !== event.currentTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lightbox = document.getElementById('chat-image-lightbox');
|
||||
if (!lightbox) return;
|
||||
|
||||
lightbox.style.display = 'none';
|
||||
|
||||
const imageEl = document.getElementById('chat-image-lightbox-img');
|
||||
if (imageEl) {
|
||||
imageEl.src = '';
|
||||
}
|
||||
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
window.handleChatImageSelected = handleChatImageSelected;
|
||||
window.openChatImageLightbox = openChatImageLightbox;
|
||||
window.closeChatImageLightbox = closeChatImageLightbox;
|
||||
updateChatImageSelectionLabel();
|
||||
|
||||
// 中文/日文等 IME 组词开始
|
||||
_contentInput.addEventListener('compositionstart', () => {
|
||||
_imeComposing = true;
|
||||
@@ -1302,9 +1470,11 @@
|
||||
const formData = new FormData(form);
|
||||
const contentInput = document.getElementById('content');
|
||||
const submitBtn = document.getElementById('send-btn');
|
||||
const imageInput = document.getElementById('chat_image');
|
||||
const selectedImage = imageInput?.files?.[0] ?? null;
|
||||
|
||||
const content = formData.get('content').trim();
|
||||
if (!content) {
|
||||
const content = String(formData.get('content') || '').trim();
|
||||
if (!content && !selectedImage) {
|
||||
contentInput.focus();
|
||||
_isSending = false;
|
||||
return;
|
||||
@@ -1312,7 +1482,7 @@
|
||||
|
||||
// 如果发言对象是 AI 小助手,也发送一份给专用机器人 API,不打断正常的发消息流程
|
||||
const toUser = formData.get('to_user');
|
||||
if (toUser === 'AI小班长') {
|
||||
if (toUser === 'AI小班长' && content) {
|
||||
sendToChatBot(content); // 异步调用,不阻塞全局发送
|
||||
}
|
||||
|
||||
@@ -1320,7 +1490,7 @@
|
||||
// 当用户输入内容符合暗号格式(4-8位大写字母/数字)时,主动尝试领取
|
||||
// 不依赖轮询标志,服务端负责校验是否真有活跃箱子及暗号是否匹配
|
||||
const passcodePattern = /^[A-Z0-9]{4,8}$/;
|
||||
if (passcodePattern.test(content.trim())) {
|
||||
if (!selectedImage && passcodePattern.test(content.trim())) {
|
||||
_isSending = false;
|
||||
|
||||
try {
|
||||
@@ -1385,6 +1555,7 @@
|
||||
const data = await response.json();
|
||||
if (response.ok && data.status === 'success') {
|
||||
contentInput.value = '';
|
||||
clearSelectedChatImage(true);
|
||||
contentInput.focus();
|
||||
} else {
|
||||
window.chatDialog.alert('发送失败: ' + (data.message || JSON.stringify(data.errors)), '操作失败',
|
||||
|
||||
Reference in New Issue
Block a user