mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-06-01 23:47:39 +08:00
@@ -6,7 +6,7 @@
|
||||
<button class="menu-btn" ref="menuBtn" @click="$emit('toggle-menu')">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
<span v-if="isMobile && unreadCount > 0" class="menu-unread-dot"></span>
|
||||
<span v-if="isMobile && unreadMessageCount > 0" class="menu-unread-dot"></span>
|
||||
</div>
|
||||
<NuxtLink class="logo-container" :to="`/`" @click="refrechData">
|
||||
<img
|
||||
@@ -47,6 +47,13 @@
|
||||
</div>
|
||||
</ToolTip>
|
||||
|
||||
<ToolTip v-if="isLogin" content="站内信" placement="bottom">
|
||||
<div class="messages-icon" @click="goToMessages">
|
||||
<i class="fas fa-envelope"></i>
|
||||
<span v-if="unreadMessageCount > 0" class="unread-badge">{{ unreadMessageCount }}</span>
|
||||
</div>
|
||||
</ToolTip>
|
||||
|
||||
<DropdownMenu v-if="isLogin" ref="userMenu" :items="headerMenuItems">
|
||||
<template #trigger>
|
||||
<div class="avatar-container">
|
||||
@@ -75,7 +82,7 @@ import DropdownMenu from '~/components/DropdownMenu.vue'
|
||||
import ToolTip from '~/components/ToolTip.vue'
|
||||
import SearchDropdown from '~/components/SearchDropdown.vue'
|
||||
import { authState, clearToken, loadCurrentUser } from '~/utils/auth'
|
||||
import { fetchUnreadCount, notificationState } from '~/utils/notification'
|
||||
import { useUnreadCount } from '~/composables/useUnreadCount'
|
||||
import { useIsMobile } from '~/utils/screen'
|
||||
import { themeState, cycleTheme, ThemeMode } from '~/utils/theme'
|
||||
import { toast } from '~/main'
|
||||
@@ -93,7 +100,7 @@ const props = defineProps({
|
||||
|
||||
const isLogin = computed(() => authState.loggedIn)
|
||||
const isMobile = useIsMobile()
|
||||
const unreadCount = computed(() => notificationState.unreadCount)
|
||||
const { count: unreadMessageCount, fetchUnreadCount } = useUnreadCount()
|
||||
const avatar = ref('')
|
||||
const showSearch = ref(false)
|
||||
const searchDropdown = ref(null)
|
||||
@@ -182,15 +189,18 @@ const goToNewPost = () => {
|
||||
}
|
||||
|
||||
const refrechData = async () => {
|
||||
await fetchUnreadCount()
|
||||
window.dispatchEvent(new Event('refresh-home'))
|
||||
}
|
||||
|
||||
const goToMessages = () => {
|
||||
navigateTo('/messages');
|
||||
};
|
||||
|
||||
const headerMenuItems = computed(() => [
|
||||
{ text: '设置', onClick: goToSettings },
|
||||
{ text: '个人主页', onClick: goToProfile },
|
||||
{ text: '退出', onClick: goToLogout },
|
||||
])
|
||||
]);
|
||||
|
||||
/** 其余逻辑保持不变 */
|
||||
const iconClass = computed(() => {
|
||||
@@ -215,9 +225,8 @@ onMounted(async () => {
|
||||
}
|
||||
const updateUnread = async () => {
|
||||
if (authState.loggedIn) {
|
||||
await fetchUnreadCount()
|
||||
} else {
|
||||
notificationState.unreadCount = 0
|
||||
// Initialize the unread count composable
|
||||
fetchUnreadCount();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,7 +235,7 @@ onMounted(async () => {
|
||||
|
||||
watch(
|
||||
() => authState.loggedIn,
|
||||
async () => {
|
||||
async (isLoggedIn) => {
|
||||
await updateAvatar()
|
||||
await updateUnread()
|
||||
},
|
||||
@@ -379,9 +388,27 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.rss-icon,
|
||||
.new-post-icon {
|
||||
.new-post-icon,
|
||||
.messages-icon {
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.unread-badge {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
right: -10px;
|
||||
background-color: #ff4d4f;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
padding: 2px 5px;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
min-width: 16px;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.rss-icon {
|
||||
|
||||
183
frontend_nuxt/components/MessageEditor.vue
Normal file
183
frontend_nuxt/components/MessageEditor.vue
Normal file
@@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<div class="message-editor-container">
|
||||
<div class="message-editor-wrapper">
|
||||
<div :id="editorId" ref="vditorElement"></div>
|
||||
</div>
|
||||
<div class="message-bottom-container">
|
||||
<div class="message-submit" :class="{ disabled: isDisabled }" @click="submit">
|
||||
<template v-if="!loading"> 发送 </template>
|
||||
<template v-else> <i class="fa-solid fa-spinner fa-spin"></i> 发送中... </template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed, onMounted, onUnmounted, ref, useId, watch } from 'vue'
|
||||
import { clearVditorStorage } from '~/utils/clearVditorStorage'
|
||||
import { themeState } from '~/utils/theme'
|
||||
import {
|
||||
createVditor,
|
||||
getEditorTheme as getEditorThemeUtil,
|
||||
getPreviewTheme as getPreviewThemeUtil,
|
||||
} from '~/utils/vditor'
|
||||
import '~/assets/global.css'
|
||||
|
||||
export default {
|
||||
name: 'MessageEditor',
|
||||
emits: ['submit'],
|
||||
props: {
|
||||
editorId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const vditorInstance = ref(null)
|
||||
const text = ref('')
|
||||
const editorId = ref(props.editorId)
|
||||
if (!editorId.value) {
|
||||
editorId.value = 'editor-' + useId()
|
||||
}
|
||||
const getEditorTheme = getEditorThemeUtil
|
||||
const getPreviewTheme = getPreviewThemeUtil
|
||||
const applyTheme = () => {
|
||||
if (vditorInstance.value) {
|
||||
vditorInstance.value.setTheme(getEditorTheme(), getPreviewTheme())
|
||||
}
|
||||
}
|
||||
|
||||
const isDisabled = computed(() => props.loading || props.disabled || !text.value.trim())
|
||||
|
||||
const submit = () => {
|
||||
if (!vditorInstance.value || isDisabled.value) return
|
||||
const value = vditorInstance.value.getValue()
|
||||
emit('submit', value, () => {
|
||||
if (!vditorInstance.value) return
|
||||
vditorInstance.value.setValue('')
|
||||
text.value = ''
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
vditorInstance.value = createVditor(editorId.value, {
|
||||
placeholder: '输入消息...',
|
||||
height: 150,
|
||||
toolbar: [
|
||||
'emoji',
|
||||
'bold',
|
||||
'italic',
|
||||
'strike',
|
||||
'link',
|
||||
'|',
|
||||
'list',
|
||||
'|',
|
||||
'line',
|
||||
'quote',
|
||||
'code',
|
||||
'inline-code',
|
||||
'|',
|
||||
'upload',
|
||||
],
|
||||
preview: {
|
||||
actions: [],
|
||||
markdown: { toc: false },
|
||||
},
|
||||
input(value) {
|
||||
text.value = value
|
||||
},
|
||||
after() {
|
||||
if (props.loading || props.disabled) {
|
||||
vditorInstance.value.disabled()
|
||||
}
|
||||
applyTheme()
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
clearVditorStorage()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.loading,
|
||||
(val) => {
|
||||
if (!vditorInstance.value) return
|
||||
if (val) {
|
||||
vditorInstance.value.disabled()
|
||||
} else if (!props.disabled) {
|
||||
vditorInstance.value.enable()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.disabled,
|
||||
(val) => {
|
||||
if (!vditorInstance.value) return
|
||||
if (val) {
|
||||
vditorInstance.value.disabled()
|
||||
} else if (!props.loading) {
|
||||
vditorInstance.value.enable()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => themeState.mode,
|
||||
() => {
|
||||
applyTheme()
|
||||
},
|
||||
)
|
||||
|
||||
return { submit, isDisabled, editorId }
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.message-editor-container {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.message-bottom-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
padding: 10px;
|
||||
background-color: var(--bg-color-soft);
|
||||
border-top: 1px solid var(--border-color);
|
||||
border-bottom-left-radius: 8px;
|
||||
border-bottom-right-radius: 8px;
|
||||
}
|
||||
|
||||
.message-submit {
|
||||
background-color: var(--primary-color);
|
||||
color: #fff;
|
||||
padding: 8px 16px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.message-submit.disabled {
|
||||
background-color: var(--primary-color-disabled);
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.message-submit:not(.disabled):hover {
|
||||
background-color: var(--primary-color-hover);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user