feat: improve realtime messaging and media handling

This commit is contained in:
winchen
2026-07-28 19:43:14 +08:00
parent 051f60aaf0
commit 6f18980a64
31 changed files with 25265 additions and 21814 deletions
+60 -3
View File
@@ -47,7 +47,13 @@
class="message-image"
@click.stop="handleImageClick(message)"
>
<img :src="message.attachment.url" :alt="message.attachment.name || '图片'">
<img
:src="message.attachment.url"
:alt="message.attachment.name || '图片'"
loading="lazy"
decoding="async"
@error="retryImageLoad"
>
</button>
<a
v-else-if="isFileMessage(message)"
@@ -63,7 +69,7 @@
<small>{{ formatFileSize(message.attachment.size) }}</small>
</span>
</a>
<p v-if="message.text">{{ message.text }}</p>
<p v-if="displayMessageText(message)">{{ displayMessageText(message) }}</p>
<time>
<span v-if="isRecallNotice(message)" class="recall-mark">{{ message.recallStatus || '已撤回' }}</span>
<span>{{ formatTime(message.displayTimestamp || message.timestamp) }}</span>
@@ -130,6 +136,8 @@
:src="imageViewer.url"
:alt="imageViewer.name || '图片'"
:style="imageViewerStyle"
decoding="async"
@error="retryImageLoad"
draggable="false"
>
</div>
@@ -325,7 +333,16 @@ const menuItems = computed(() => {
function scrollToBottom() {
nextTick(() => {
if (listEl.value) {
listEl.value.scrollTop = listEl.value.scrollHeight;
const scroll = () => {
if (listEl.value) {
listEl.value.scrollTop = listEl.value.scrollHeight;
}
};
scroll();
requestAnimationFrame(() => {
scroll();
requestAnimationFrame(scroll);
});
}
});
}
@@ -372,6 +389,22 @@ function messageIdentity(message) {
return String(message.messageUid || message.id || '');
}
function displayMessageText(message) {
const text = String(message?.text || '').replace(/^引用:.*(?:\r?\n|$)/, '').trim();
const contentType = Number(message?.contentType || 0);
if (!text) {
return '';
}
if (contentType !== 1 && text === '[暂不支持的消息类型]') {
return '';
}
if ((contentType === 3 || message?.type === 'image') && (text === '[图片]' || text === '图片')) {
return '';
}
return text;
}
function canOpenMessageMenu(message) {
return message && message.status !== 'failed' && message.status !== 'sending';
}
@@ -552,6 +585,30 @@ function formatFileSize(size) {
return bytes > 0 ? `${bytes} B` : '';
}
function retryImageLoad(event) {
const image = event.currentTarget;
if (!(image instanceof HTMLImageElement)) {
return;
}
const attempts = Number(image.dataset.loadRetries || 0);
if (attempts >= 2) {
return;
}
const originalUrl = image.dataset.originalUrl || image.getAttribute('src') || '';
if (!originalUrl) {
return;
}
image.dataset.originalUrl = originalUrl;
image.dataset.loadRetries = String(attempts + 1);
window.setTimeout(() => {
if (!image.isConnected) {
return;
}
const separator = originalUrl.includes('?') ? '&' : '?';
image.src = `${originalUrl}${separator}_image_retry=${attempts + 1}`;
}, 500 * (attempts + 1));
}
function handleMessageClick(message) {
if (props.selectionMode) {
emit('toggle-select', message);