mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-06-08 23:06:08 +00:00
Compare commits
4 Commits
codex/upda
...
feature/ch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2288522372 | ||
|
|
a6d8add5fa | ||
|
|
ad481cffca | ||
|
|
68a82fa2ec |
@@ -1,33 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="new-message-container" :style="{ bottom: bottom + 'px' }" @click="$emit('click')">
|
|
||||||
{{ count }} 条新消息,点击查看
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
const props = defineProps({
|
|
||||||
count: {
|
|
||||||
type: Number,
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
bottom: {
|
|
||||||
type: Number,
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.new-message-container {
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
color: #fff;
|
|
||||||
padding: 6px 16px;
|
|
||||||
border-radius: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
||||||
z-index: 50;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -36,7 +36,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="item.replyTo" class="reply-preview info-content-text">
|
<div v-if="item.replyTo" class="reply-preview info-content-text">
|
||||||
<div class="reply-author">{{ item.replyTo.sender.username }}</div>
|
<div class="reply-header">
|
||||||
|
<next class="reply-icon" />
|
||||||
|
<BaseImage class="reply-avatar" :src="item.replyTo.sender.avatar" alt="avatar" />
|
||||||
|
<div class="reply-author">{{ item.replyTo.sender.username }}:</div>
|
||||||
|
</div>
|
||||||
<div class="reply-content" v-html="renderMarkdown(item.replyTo.content)"></div>
|
<div class="reply-content" v-html="renderMarkdown(item.replyTo.content)"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="message-content">
|
<div class="message-content">
|
||||||
@@ -62,19 +66,22 @@
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<NewMessageContainer
|
<div class="message-input-area">
|
||||||
v-if="showNewMessageContainer"
|
<div
|
||||||
:count="newMessagesCount"
|
v-if="newMessagesCount > 0 && !isUserNearBottom"
|
||||||
:bottom="inputAreaHeight + 20"
|
class="new-message-container"
|
||||||
@click="handleNewMessagesClick"
|
@click="handleScrollToBottom"
|
||||||
/>
|
>
|
||||||
|
<double-down />
|
||||||
|
<div class="new-message-count">有{{ newMessagesCount }}条新消息</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="message-input-area" ref="messageInputAreaEl">
|
|
||||||
<div v-if="replyTo" class="active-reply">
|
<div v-if="replyTo" class="active-reply">
|
||||||
正在回复 {{ replyTo.sender.username }}:
|
正在回复 {{ replyTo.sender.username }}:
|
||||||
{{ stripMarkdownLength(replyTo.content, 50) }}
|
{{ stripMarkdownLength(replyTo.content, 50) }}
|
||||||
<close-icon class="close-reply" @click="replyTo = null" />
|
<close-icon class="close-reply" @click="replyTo = null" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MessageEditor :loading="sending" @submit="sendMessage" />
|
<MessageEditor :loading="sending" @submit="sendMessage" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -103,7 +110,6 @@ import { useChannelsUnreadCount } from '~/composables/useChannelsUnreadCount'
|
|||||||
import TimeManager from '~/utils/time'
|
import TimeManager from '~/utils/time'
|
||||||
import BaseTimeline from '~/components/BaseTimeline.vue'
|
import BaseTimeline from '~/components/BaseTimeline.vue'
|
||||||
import BasePlaceholder from '~/components/BasePlaceholder.vue'
|
import BasePlaceholder from '~/components/BasePlaceholder.vue'
|
||||||
import NewMessageContainer from '~/components/NewMessageContainer.vue'
|
|
||||||
|
|
||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -120,7 +126,6 @@ const error = ref(null)
|
|||||||
const conversationId = route.params.id
|
const conversationId = route.params.id
|
||||||
const currentUser = ref(null)
|
const currentUser = ref(null)
|
||||||
const messagesListEl = ref(null)
|
const messagesListEl = ref(null)
|
||||||
const messageInputAreaEl = ref(null)
|
|
||||||
const currentPage = ref(0)
|
const currentPage = ref(0)
|
||||||
const totalPages = ref(0)
|
const totalPages = ref(0)
|
||||||
const loadingMore = ref(false)
|
const loadingMore = ref(false)
|
||||||
@@ -130,20 +135,6 @@ const isFloatMode = computed(() => route.query.float !== undefined)
|
|||||||
const floatRoute = useState('messageFloatRoute')
|
const floatRoute = useState('messageFloatRoute')
|
||||||
const replyTo = ref(null)
|
const replyTo = ref(null)
|
||||||
const newMessagesCount = ref(0)
|
const newMessagesCount = ref(0)
|
||||||
const inputAreaHeight = ref(0)
|
|
||||||
const showNewMessageContainer = computed(
|
|
||||||
() => newMessagesCount.value > 0 && !isUserNearBottom.value,
|
|
||||||
)
|
|
||||||
|
|
||||||
function updateInputAreaHeight() {
|
|
||||||
if (!messageInputAreaEl.value) return
|
|
||||||
inputAreaHeight.value = messageInputAreaEl.value.offsetHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleNewMessagesClick() {
|
|
||||||
scrollToBottomSmooth()
|
|
||||||
newMessagesCount.value = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
const isUserNearBottom = ref(true)
|
const isUserNearBottom = ref(true)
|
||||||
function updateNearBottom() {
|
function updateNearBottom() {
|
||||||
@@ -151,6 +142,9 @@ function updateNearBottom() {
|
|||||||
if (!el) return
|
if (!el) return
|
||||||
const threshold = 40 // px
|
const threshold = 40 // px
|
||||||
isUserNearBottom.value = el.scrollHeight - el.scrollTop - el.clientHeight <= threshold
|
isUserNearBottom.value = el.scrollHeight - el.scrollTop - el.clientHeight <= threshold
|
||||||
|
if (isUserNearBottom.value) {
|
||||||
|
newMessagesCount.value = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasMoreMessages = computed(() => currentPage.value < totalPages.value - 1)
|
const hasMoreMessages = computed(() => currentPage.value < totalPages.value - 1)
|
||||||
@@ -194,6 +188,11 @@ function scrollToBottomInstant() {
|
|||||||
el.scrollTop = el.scrollHeight
|
el.scrollTop = el.scrollHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleScrollToBottom() {
|
||||||
|
scrollToBottomSmooth()
|
||||||
|
newMessagesCount.value = 0
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchMessages(page = 0) {
|
async function fetchMessages(page = 0) {
|
||||||
if (page === 0) {
|
if (page === 0) {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@@ -325,6 +324,7 @@ async function sendMessage(content, clearInput) {
|
|||||||
await nextTick()
|
await nextTick()
|
||||||
// 仅“发送消息成功后”才平滑滚动到底部
|
// 仅“发送消息成功后”才平滑滚动到底部
|
||||||
scrollToBottomSmooth()
|
scrollToBottomSmooth()
|
||||||
|
newMessagesCount.value = 0
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(e.message)
|
toast.error(e.message)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -353,10 +353,6 @@ onMounted(async () => {
|
|||||||
messagesListEl.value.addEventListener('scroll', updateNearBottom, { passive: true })
|
messagesListEl.value.addEventListener('scroll', updateNearBottom, { passive: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('resize', updateInputAreaHeight)
|
|
||||||
await nextTick()
|
|
||||||
updateInputAreaHeight()
|
|
||||||
|
|
||||||
currentUser.value = await fetchCurrentUser()
|
currentUser.value = await fetchCurrentUser()
|
||||||
if (currentUser.value) {
|
if (currentUser.value) {
|
||||||
await fetchMessages(0)
|
await fetchMessages(0)
|
||||||
@@ -398,6 +394,7 @@ const subscribeToConversation = () => {
|
|||||||
|
|
||||||
await markConversationAsRead()
|
await markConversationAsRead()
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
||||||
if (isUserNearBottom.value) {
|
if (isUserNearBottom.value) {
|
||||||
scrollToBottomSmooth()
|
scrollToBottomSmooth()
|
||||||
} else {
|
} else {
|
||||||
@@ -415,14 +412,6 @@ watch(isConnected, (newValue) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(isUserNearBottom, (val) => {
|
|
||||||
if (val) newMessagesCount.value = 0
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(replyTo, () => {
|
|
||||||
nextTick(updateInputAreaHeight)
|
|
||||||
})
|
|
||||||
|
|
||||||
onActivated(async () => {
|
onActivated(async () => {
|
||||||
// 返回页面时:刷新数据与已读,并滚动到底部
|
// 返回页面时:刷新数据与已读,并滚动到底部
|
||||||
if (currentUser.value) {
|
if (currentUser.value) {
|
||||||
@@ -455,7 +444,6 @@ onUnmounted(() => {
|
|||||||
if (messagesListEl.value) {
|
if (messagesListEl.value) {
|
||||||
messagesListEl.value.removeEventListener('scroll', updateNearBottom)
|
messagesListEl.value.removeEventListener('scroll', updateNearBottom)
|
||||||
}
|
}
|
||||||
window.removeEventListener('resize', updateInputAreaHeight)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
function minimize() {
|
function minimize() {
|
||||||
@@ -593,6 +581,25 @@ function goBack() {
|
|||||||
gap: 10px;
|
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 {
|
.user-name {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -623,11 +630,6 @@ function goBack() {
|
|||||||
border-bottom-left-radius: 4px;
|
border-bottom-left-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-input-area {
|
|
||||||
margin-left: 20px;
|
|
||||||
margin-right: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-container {
|
.loading-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -644,6 +646,19 @@ function goBack() {
|
|||||||
.message-input-area {
|
.message-input-area {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
margin-right: 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 {
|
.reply-preview {
|
||||||
@@ -655,9 +670,16 @@ function goBack() {
|
|||||||
background-color: var(--normal-light-background-color);
|
background-color: var(--normal-light-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.reply-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.reply-author {
|
.reply-author {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reply-btn {
|
.reply-btn {
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ import {
|
|||||||
RobotOne,
|
RobotOne,
|
||||||
Server,
|
Server,
|
||||||
Protection,
|
Protection,
|
||||||
|
DoubleDown,
|
||||||
} from '@icon-park/vue-next'
|
} from '@icon-park/vue-next'
|
||||||
|
|
||||||
export default defineNuxtPlugin((nuxtApp) => {
|
export default defineNuxtPlugin((nuxtApp) => {
|
||||||
@@ -149,4 +150,5 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|||||||
nuxtApp.vueApp.component('RobotOne', RobotOne)
|
nuxtApp.vueApp.component('RobotOne', RobotOne)
|
||||||
nuxtApp.vueApp.component('ServerIcon', Server)
|
nuxtApp.vueApp.component('ServerIcon', Server)
|
||||||
nuxtApp.vueApp.component('Protection', Protection)
|
nuxtApp.vueApp.component('Protection', Protection)
|
||||||
|
nuxtApp.vueApp.component('DoubleDown', DoubleDown)
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user