diff --git a/frontend_nuxt/pages/message-box/[id].vue b/frontend_nuxt/pages/message-box/[id].vue index 4e07ac7da..58e43962e 100644 --- a/frontend_nuxt/pages/message-box/[id].vue +++ b/frontend_nuxt/pages/message-box/[id].vue @@ -36,7 +36,11 @@
-
{{ item.replyTo.sender.username }}
+
+ + +
{{ item.replyTo.sender.username }}:
+
@@ -63,11 +67,21 @@
+
+ +
有{{ newMessagesCount }}条新消息
+
+
正在回复 {{ replyTo.sender.username }}: {{ stripMarkdownLength(replyTo.content, 50) }}
+
@@ -120,6 +134,7 @@ const isChannel = ref(false) const isFloatMode = computed(() => route.query.float !== undefined) const floatRoute = useState('messageFloatRoute') const replyTo = ref(null) +const newMessagesCount = ref(0) const isUserNearBottom = ref(true) function updateNearBottom() { @@ -127,6 +142,9 @@ function updateNearBottom() { if (!el) return const threshold = 40 // px isUserNearBottom.value = el.scrollHeight - el.scrollTop - el.clientHeight <= threshold + if (isUserNearBottom.value) { + newMessagesCount.value = 0 + } } const hasMoreMessages = computed(() => currentPage.value < totalPages.value - 1) @@ -170,6 +188,11 @@ function scrollToBottomInstant() { el.scrollTop = el.scrollHeight } +function handleScrollToBottom() { + scrollToBottomSmooth() + newMessagesCount.value = 0 +} + async function fetchMessages(page = 0) { if (page === 0) { loading.value = true @@ -301,6 +324,7 @@ async function sendMessage(content, clearInput) { await nextTick() // 仅“发送消息成功后”才平滑滚动到底部 scrollToBottomSmooth() + newMessagesCount.value = 0 } catch (e) { toast.error(e.message) } finally { @@ -373,6 +397,8 @@ const subscribeToConversation = () => { if (isUserNearBottom.value) { scrollToBottomSmooth() + } else { + newMessagesCount.value += 1 } } catch (e) { console.error('Failed to parse websocket message', e) @@ -454,7 +480,6 @@ function goBack() { } .chat-container.float { - height: 100vh; } .chat-header { @@ -555,6 +580,25 @@ function goBack() { gap: 10px; } +.new-message-container { + display: flex; + flex-direction: row; + align-items: center; + gap: 5px; + cursor: pointer; + border: 1px solid var(--normal-border-color); + border-radius: 20px; + padding: 3px 6px; + box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1); + width: fit-content; + position: absolute; + bottom: calc(100% + 20px); + left: 50%; + transform: translateX(-50%); + z-index: 10; + background-color: var(--background-color); +} + .user-name { font-size: 14px; font-weight: 600; @@ -585,11 +629,6 @@ function goBack() { border-bottom-left-radius: 4px; } -.message-input-area { - margin-left: 20px; - margin-right: 20px; -} - .loading-container { display: flex; justify-content: center; @@ -606,6 +645,19 @@ function goBack() { .message-input-area { margin-left: 10px; margin-right: 10px; + position: relative; +} + +.reply-icon { + color: var(--primary-color); + margin-right: 5px; +} + +.reply-avatar { + width: 20px; + height: 20px; + border-radius: 50%; + margin-right: 5px; } .reply-preview { @@ -617,9 +669,16 @@ function goBack() { background-color: var(--normal-light-background-color); } +.reply-header { + display: flex; + flex-direction: row; + align-items: center; +} + .reply-author { font-weight: bold; margin-bottom: 2px; + opacity: 0.5; } .reply-btn { diff --git a/frontend_nuxt/plugins/iconpark.client.ts b/frontend_nuxt/plugins/iconpark.client.ts index 33efc8a73..ab87cc6bf 100644 --- a/frontend_nuxt/plugins/iconpark.client.ts +++ b/frontend_nuxt/plugins/iconpark.client.ts @@ -73,6 +73,7 @@ import { RobotOne, Server, Protection, + DoubleDown, } from '@icon-park/vue-next' export default defineNuxtPlugin((nuxtApp) => { @@ -149,4 +150,5 @@ export default defineNuxtPlugin((nuxtApp) => { nuxtApp.vueApp.component('RobotOne', RobotOne) nuxtApp.vueApp.component('ServerIcon', Server) nuxtApp.vueApp.component('Protection', Protection) + nuxtApp.vueApp.component('DoubleDown', DoubleDown) })