UI升级
This commit is contained in:
@@ -1,50 +1,62 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
id="msgListContainer"
|
id="msgListContainer"
|
||||||
ref="containerRef"
|
ref="containerRef"
|
||||||
class="flex-1 overflow-y-auto p-4 space-y-5"
|
class="flex-1 overflow-y-auto p-4 space-y-5"
|
||||||
@dragover.prevent
|
@dragover.prevent
|
||||||
@drop.prevent
|
@drop.prevent
|
||||||
@scroll="handleScroll"
|
@scroll="handleScroll"
|
||||||
>
|
>
|
||||||
<!-- 加载历史消息提示 -->
|
<!-- 顶部加载状态提示区 -->
|
||||||
<div v-if="loadingHistory" class="flex justify-center items-center py-4">
|
<div class="w-full flex justify-center py-2 min-h-[40px] items-center">
|
||||||
<div class="flex items-center gap-2 text-gray-400 text-sm">
|
|
||||||
<i class="fas fa-spinner fa-spin"></i>
|
<!-- 1. 加载中 -->
|
||||||
<span>正在加载历史消息...</span>
|
<div v-if="loadingHistory" class="flex items-center gap-2 text-primary/80 text-xs bg-black/20 px-3 py-1.5 rounded-full backdrop-blur-sm">
|
||||||
|
<i class="fas fa-circle-notch fa-spin"></i>
|
||||||
|
<span>加载历史记录...</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 2. 点击加载 (还有更多消息但未触发自动加载时显示) -->
|
||||||
|
<button
|
||||||
|
v-else-if="hasMoreHistory"
|
||||||
|
class="flex items-center gap-2 text-gray-500 hover:text-primary text-xs bg-black/10 hover:bg-white/10 px-3 py-1.5 rounded-full transition-all duration-200 cursor-pointer"
|
||||||
|
@click="$emit('load-more')"
|
||||||
|
>
|
||||||
|
<i class="fas fa-history"></i>
|
||||||
|
<span>点击加载历史消息</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- 3. 已经到顶 -->
|
||||||
|
<div v-else-if="messages.length > 0" class="flex items-center gap-2 text-gray-600 text-[10px] opacity-50">
|
||||||
|
<span class="w-8 h-px bg-gray-700"></span>
|
||||||
|
<span>已经到顶了</span>
|
||||||
|
<span class="w-8 h-px bg-gray-700"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 已经到顶提示 -->
|
|
||||||
<div v-else-if="!hasMoreHistory && messages.length > 0" class="flex justify-center items-center py-4">
|
|
||||||
<div class="text-gray-500 text-sm">
|
|
||||||
<span>已经到顶啦~</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-for="(msg, index) in messages"
|
v-for="(msg, index) in messages"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="flex gap-3 msg-row group"
|
class="flex gap-3 msg-row group"
|
||||||
:class="{ 'flex-row-reverse': msg.isSelf }"
|
:class="{ 'flex-row-reverse': msg.isSelf }"
|
||||||
@contextmenu.stop="$emit('contextmenu', $event, msg)"
|
@contextmenu.stop="$emit('contextmenu', $event, msg)"
|
||||||
>
|
>
|
||||||
<!-- 头像 -->
|
<!-- 头像 -->
|
||||||
<Avatar
|
<Avatar
|
||||||
:name="msg.isSelf ? currentUser.name : (target.user?.name || target.remark_name)"
|
:name="msg.isSelf ? currentUser.name : (target.user?.name || target.remark_name)"
|
||||||
:avatar="msg.isSelf ? currentUser.avatar : (target.user?.avatar || target.remark_name?.charAt(0))"
|
:avatar="msg.isSelf ? currentUser.avatar : (target.user?.avatar || target.remark_name?.charAt(0))"
|
||||||
:color="msg.isSelf ? generateColor(currentUser.id) : target.color"
|
:color="msg.isSelf ? generateColor(currentUser.id) : target.color"
|
||||||
size="sm"
|
size="sm"
|
||||||
rounded="lg"
|
rounded="lg"
|
||||||
class="cursor-pointer hover:opacity-80 transition self-start mt-1"
|
class="cursor-pointer hover:opacity-80 transition self-start mt-1 shadow-md"
|
||||||
@click="$emit('avatar-click', msg.isSelf ? currentUser : target)"
|
@click="$emit('avatar-click', msg.isSelf ? currentUser : target)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 消息内容区 -->
|
<!-- 消息内容区 -->
|
||||||
<div class="flex flex-col max-w-[75%] md:max-w-[60%]">
|
<div class="flex flex-col max-w-[75%] md:max-w-[60%]">
|
||||||
<div
|
<div
|
||||||
class="text-[10px] text-gray-500 mb-1 px-1"
|
class="text-[10px] text-gray-500 mb-1 px-1"
|
||||||
:class="{ 'text-right': msg.isSelf }"
|
:class="{ 'text-right': msg.isSelf }"
|
||||||
>
|
>
|
||||||
{{ msg.isSelf ? 'Me' : (target.remark_name || target.user?.name) }}
|
{{ msg.isSelf ? 'Me' : (target.remark_name || target.user?.name) }}
|
||||||
</div>
|
</div>
|
||||||
@@ -109,3 +121,19 @@ defineExpose({
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 隐藏滚动条但保留功能 */
|
||||||
|
.overflow-y-auto::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
.overflow-y-auto::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.overflow-y-auto::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
.overflow-y-auto:hover::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -198,8 +198,8 @@
|
|||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<!-- 默认空页面 -->
|
<!-- 默认空页面 -->
|
||||||
<div
|
<div
|
||||||
v-if="contactStore.leftPanelMode === 'default' && !contactStore.selectedContact"
|
v-if="contactStore.leftPanelMode === 'default' && !contactStore.selectedContact"
|
||||||
class="flex-1 flex flex-col items-center justify-center bg-dark text-gray-500 h-full"
|
class="flex-1 flex flex-col items-center justify-center bg-dark text-gray-500 h-full"
|
||||||
>
|
>
|
||||||
<div class="w-32 h-32 bg-panel rounded-full flex items-center justify-center mb-6 shadow-2xl border border-gray-800 animate-float">
|
<div class="w-32 h-32 bg-panel rounded-full flex items-center justify-center mb-6 shadow-2xl border border-gray-800 animate-float">
|
||||||
<i class="fas fa-address-book text-5xl text-primary opacity-80"></i>
|
<i class="fas fa-address-book text-5xl text-primary opacity-80"></i>
|
||||||
@@ -210,8 +210,8 @@
|
|||||||
|
|
||||||
<!-- 好友资料卡视图 -->
|
<!-- 好友资料卡视图 -->
|
||||||
<ContactDetailCard
|
<ContactDetailCard
|
||||||
v-else-if="contactStore.selectedContact"
|
v-else-if="contactStore.selectedContact"
|
||||||
@switch-to-chat="currentTab = 'chat'"
|
@switch-to-chat="currentTab = 'chat'"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 好友管理器视图 -->
|
<!-- 好友管理器视图 -->
|
||||||
@@ -220,13 +220,8 @@
|
|||||||
<!-- 好友通知视图 -->
|
<!-- 好友通知视图 -->
|
||||||
<FriendNotifyView v-else-if="contactStore.leftPanelMode === 'friend-notify'" />
|
<FriendNotifyView v-else-if="contactStore.leftPanelMode === 'friend-notify'" />
|
||||||
|
|
||||||
<!-- 群通知视图(预留) -->
|
<!-- 群通知视图 -->
|
||||||
<div v-else-if="contactStore.leftPanelMode === 'group-notify'" class="flex-1 flex items-center justify-center text-gray-400">
|
<GroupNotifyView v-else-if="contactStore.leftPanelMode === 'group-notify'" />
|
||||||
<div class="text-center">
|
|
||||||
<i class="fas fa-users text-6xl mb-4 opacity-50"></i>
|
|
||||||
<p class="text-sm">群通知功能开发中</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ContextMenu />
|
<ContextMenu />
|
||||||
@@ -280,6 +275,7 @@ import ContactView from '@/views/contact/ContactView.vue'
|
|||||||
import ContactCircle from '@/views/contact/ContactCircle.vue'
|
import ContactCircle from '@/views/contact/ContactCircle.vue'
|
||||||
import ContactDetailCard from '@/views/contact/ContactDetailCard.vue'
|
import ContactDetailCard from '@/views/contact/ContactDetailCard.vue'
|
||||||
import FriendNotifyView from '@/views/contact/FriendNotifyView.vue'
|
import FriendNotifyView from '@/views/contact/FriendNotifyView.vue'
|
||||||
|
import GroupNotifyView from '@/views/contact/GroupNotifyView.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
@@ -385,7 +381,7 @@ async function selectChat(contact: Contact) {
|
|||||||
try {
|
try {
|
||||||
const response = await messageApi.getMessages(roomId, 1, 50)
|
const response = await messageApi.getMessages(roomId, 1, 50)
|
||||||
console.log('Messages API response:', response)
|
console.log('Messages API response:', response)
|
||||||
|
|
||||||
// response 已经是 PaginatedResponse,直接访问 data
|
// response 已经是 PaginatedResponse,直接访问 data
|
||||||
if (response && response.data && Array.isArray(response.data)) {
|
if (response && response.data && Array.isArray(response.data)) {
|
||||||
const msgs = response.data.map((msg: ChatMessage) => ({
|
const msgs = response.data.map((msg: ChatMessage) => ({
|
||||||
@@ -394,17 +390,17 @@ async function selectChat(contact: Contact) {
|
|||||||
extra: typeof msg.extra === 'string' ? JSON.parse(msg.extra || '{}') : msg.extra,
|
extra: typeof msg.extra === 'string' ? JSON.parse(msg.extra || '{}') : msg.extra,
|
||||||
}))
|
}))
|
||||||
console.log('Processed messages:', msgs)
|
console.log('Processed messages:', msgs)
|
||||||
|
|
||||||
// 确保消息按时间顺序排列(reverse 后最新的在最后)
|
// 确保消息按时间顺序排列(reverse 后最新的在最后)
|
||||||
const sortedMsgs = msgs.reverse()
|
const sortedMsgs = msgs.reverse()
|
||||||
chatStore.setRoomMessages(roomId, sortedMsgs)
|
chatStore.setRoomMessages(roomId, sortedMsgs)
|
||||||
console.log('Messages set to store, roomId:', roomId, 'count:', sortedMsgs.length)
|
console.log('Messages set to store, roomId:', roomId, 'count:', sortedMsgs.length)
|
||||||
|
|
||||||
// 如果返回的消息数量少于 pageSize,说明没有更多历史消息了
|
// 如果返回的消息数量少于 pageSize,说明没有更多历史消息了
|
||||||
if (response.data.length < 50) {
|
if (response.data.length < 50) {
|
||||||
hasMoreHistory.value[roomId] = false
|
hasMoreHistory.value[roomId] = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 等待 DOM 更新后再滚动
|
// 等待 DOM 更新后再滚动
|
||||||
await nextTick()
|
await nextTick()
|
||||||
scrollToBottom(false) // 初始进入直接跳到底部
|
scrollToBottom(false) // 初始进入直接跳到底部
|
||||||
@@ -412,7 +408,7 @@ async function selectChat(contact: Contact) {
|
|||||||
console.warn('No messages in response:', response)
|
console.warn('No messages in response:', response)
|
||||||
hasMoreHistory.value[roomId] = false
|
hasMoreHistory.value[roomId] = false
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load messages:', error)
|
console.error('Failed to load messages:', error)
|
||||||
toastStore.error('加载消息失败')
|
toastStore.error('加载消息失败')
|
||||||
hasMoreHistory.value[roomId] = false
|
hasMoreHistory.value[roomId] = false
|
||||||
@@ -428,48 +424,48 @@ async function selectChat(contact: Contact) {
|
|||||||
// 加载更多历史消息(触顶加载)
|
// 加载更多历史消息(触顶加载)
|
||||||
async function loadMoreMessages() {
|
async function loadMoreMessages() {
|
||||||
if (!chatStore.currentTarget || loadingHistory.value) return
|
if (!chatStore.currentTarget || loadingHistory.value) return
|
||||||
|
|
||||||
const roomId = getRoomId(chatStore.currentTarget)
|
const roomId = getRoomId(chatStore.currentTarget)
|
||||||
const currentMessages = chatStore.getRoomMessages(roomId)
|
const currentMessages = chatStore.getRoomMessages(roomId)
|
||||||
|
|
||||||
// 如果当前没有消息,不加载
|
// 如果当前没有消息,不加载
|
||||||
if (currentMessages.length === 0) return
|
if (currentMessages.length === 0) return
|
||||||
|
|
||||||
// 获取当前页码,如果没有则初始化为1
|
// 获取当前页码,如果没有则初始化为1
|
||||||
const page = (currentPage.value[roomId] || 1) + 1
|
const page = (currentPage.value[roomId] || 1) + 1
|
||||||
|
|
||||||
loadingHistory.value = true
|
loadingHistory.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 记录加载前的滚动位置和第一条消息的高度
|
// 记录加载前的滚动位置和第一条消息的高度
|
||||||
const container = document.getElementById('msgListContainer')
|
const container = document.getElementById('msgListContainer')
|
||||||
const oldScrollTop = container?.scrollTop || 0
|
const oldScrollTop = container?.scrollTop || 0
|
||||||
const oldScrollHeight = container?.scrollHeight || 0
|
const oldScrollHeight = container?.scrollHeight || 0
|
||||||
|
|
||||||
// 加载历史消息
|
// 加载历史消息
|
||||||
const pageSize = 50
|
const pageSize = 50
|
||||||
const response = await messageApi.getMessages(roomId, page, pageSize)
|
const response = await messageApi.getMessages(roomId, page, pageSize)
|
||||||
|
|
||||||
if (response.data && response.data.length > 0) {
|
if (response.data && response.data.length > 0) {
|
||||||
const newMsgs = response.data.map((msg: ChatMessage) => ({
|
const newMsgs = response.data.map((msg: ChatMessage) => ({
|
||||||
...msg,
|
...msg,
|
||||||
isSelf: msg.sender_user_id === authStore.user!.id,
|
isSelf: msg.sender_user_id === authStore.user!.id,
|
||||||
extra: typeof msg.extra === 'string' ? JSON.parse(msg.extra || '{}') : msg.extra,
|
extra: typeof msg.extra === 'string' ? JSON.parse(msg.extra || '{}') : msg.extra,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// 将新消息添加到现有消息数组的前面(历史消息)
|
// 将新消息添加到现有消息数组的前面(历史消息)
|
||||||
const existingMessages = [...currentMessages]
|
const existingMessages = [...currentMessages]
|
||||||
const allMessages = [...newMsgs.reverse(), ...existingMessages]
|
const allMessages = [...newMsgs.reverse(), ...existingMessages]
|
||||||
chatStore.setRoomMessages(roomId, allMessages)
|
chatStore.setRoomMessages(roomId, allMessages)
|
||||||
|
|
||||||
// 更新页码
|
// 更新页码
|
||||||
currentPage.value[roomId] = page
|
currentPage.value[roomId] = page
|
||||||
|
|
||||||
// 如果返回的消息数量少于 pageSize,说明没有更多历史消息了
|
// 如果返回的消息数量少于 pageSize,说明没有更多历史消息了
|
||||||
if (response.data.length < pageSize) {
|
if (response.data.length < pageSize) {
|
||||||
hasMoreHistory.value[roomId] = false
|
hasMoreHistory.value[roomId] = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 等待DOM更新后恢复滚动位置
|
// 等待DOM更新后恢复滚动位置
|
||||||
await nextTick()
|
await nextTick()
|
||||||
if (container) {
|
if (container) {
|
||||||
@@ -494,7 +490,7 @@ async function loadMoreMessages() {
|
|||||||
async function selectChatByConversation(conv: Conversation) {
|
async function selectChatByConversation(conv: Conversation) {
|
||||||
// 在联系人列表中找到对应联系人(用于沿用现有 chatStore 结构)
|
// 在联系人列表中找到对应联系人(用于沿用现有 chatStore 结构)
|
||||||
const contact = chatStore.contacts.find(
|
const contact = chatStore.contacts.find(
|
||||||
c => c.user_id === conv.target_id || c.id === conv.target_id,
|
c => c.user_id === conv.target_id || c.id === conv.target_id,
|
||||||
)
|
)
|
||||||
if (!contact) {
|
if (!contact) {
|
||||||
// 如果本地没有联系人数据,可以在这里补一次联系人列表加载;当前先简单返回
|
// 如果本地没有联系人数据,可以在这里补一次联系人列表加载;当前先简单返回
|
||||||
@@ -545,11 +541,11 @@ function handleWebSocketMessage(message: ChatMessage) {
|
|||||||
|
|
||||||
if (contact) {
|
if (contact) {
|
||||||
chatStore.updateContactLastMsg(contact.id, getMsgSummary(message), Date.now())
|
chatStore.updateContactLastMsg(contact.id, getMsgSummary(message), Date.now())
|
||||||
|
|
||||||
// 检查是否是当前选中的聊天(通过 room_id 匹配)
|
// 检查是否是当前选中的聊天(通过 room_id 匹配)
|
||||||
const currentRoomId = chatStore.currentTarget ? getRoomId(chatStore.currentTarget) : null
|
const currentRoomId = chatStore.currentTarget ? getRoomId(chatStore.currentTarget) : null
|
||||||
const isCurrentChat = currentRoomId === roomId
|
const isCurrentChat = currentRoomId === roomId
|
||||||
|
|
||||||
conversationStore.handleMessageUpdate(message, message.isSelf === true, isCurrentChat)
|
conversationStore.handleMessageUpdate(message, message.isSelf === true, isCurrentChat)
|
||||||
if (!message.isSelf) {
|
if (!message.isSelf) {
|
||||||
// 如果不是当前聊天窗口,或者是当前窗口但用户不在底部
|
// 如果不是当前聊天窗口,或者是当前窗口但用户不在底部
|
||||||
@@ -580,7 +576,7 @@ function handleWebSocketMessage(message: ChatMessage) {
|
|||||||
function backToList() { if (isMobile.value) chatVisible.value = false }
|
function backToList() { if (isMobile.value) chatVisible.value = false }
|
||||||
function handleLogout() { authStore.logout(); wsManager.disconnect(); router.push('/login') }
|
function handleLogout() { authStore.logout(); wsManager.disconnect(); router.push('/login') }
|
||||||
function getMsgSummary(msg: ChatMessage): string { const types: Record<number, string> = { 1: '[图片]', 2: '[语音]', 3: '[视频]', 8: '[文件]' }; return types[msg.message_type] || msg.content }
|
function getMsgSummary(msg: ChatMessage): string { const types: Record<number, string> = { 1: '[图片]', 2: '[语音]', 3: '[视频]', 8: '[文件]' }; return types[msg.message_type] || msg.content }
|
||||||
async function startCall(type: 'audio' | 'video') {
|
async function startCall(type: 'audio' | 'video') {
|
||||||
if (!chatStore.currentTarget) return
|
if (!chatStore.currentTarget) return
|
||||||
const receiverUserId = chatStore.currentTarget.user_id || chatStore.currentTarget.id
|
const receiverUserId = chatStore.currentTarget.user_id || chatStore.currentTarget.id
|
||||||
// 直接传入 room_id
|
// 直接传入 room_id
|
||||||
@@ -640,11 +636,11 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
await loadContacts()
|
await loadContacts()
|
||||||
await conversationStore.loadConversations()
|
await conversationStore.loadConversations()
|
||||||
|
|
||||||
// 恢复选中的会话(优先使用 room_id 查找)
|
// 恢复选中的会话(优先使用 room_id 查找)
|
||||||
const savedRoomId = storage.getSelectedRoomId()
|
const savedRoomId = storage.getSelectedRoomId()
|
||||||
const savedTargetId = storage.getSelectedConversation()
|
const savedTargetId = storage.getSelectedConversation()
|
||||||
|
|
||||||
if (savedRoomId) {
|
if (savedRoomId) {
|
||||||
// 优先使用 room_id 查找联系人
|
// 优先使用 room_id 查找联系人
|
||||||
const contact = chatStore.contacts.find(c => {
|
const contact = chatStore.contacts.find(c => {
|
||||||
@@ -656,7 +652,7 @@ onMounted(async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果没有 room_id 或找不到,使用 targetId 查找
|
// 如果没有 room_id 或找不到,使用 targetId 查找
|
||||||
if (savedTargetId) {
|
if (savedTargetId) {
|
||||||
const contact = chatStore.contacts.find(c => c.user_id === savedTargetId || c.id === savedTargetId)
|
const contact = chatStore.contacts.find(c => c.user_id === savedTargetId || c.id === savedTargetId)
|
||||||
@@ -664,7 +660,7 @@ onMounted(async () => {
|
|||||||
await selectChat(contact)
|
await selectChat(contact)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('resize', () => isMobile.value = window.innerWidth < 768)
|
window.addEventListener('resize', () => isMobile.value = window.innerWidth < 768)
|
||||||
})
|
})
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
|||||||
29
src/views/contact/GroupNotifyView.vue
Normal file
29
src/views/contact/GroupNotifyView.vue
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex-1 flex flex-col bg-panel h-full">
|
||||||
|
<!-- 头部 -->
|
||||||
|
<div class="p-6 border-b border-gray-800/50 flex justify-between items-center">
|
||||||
|
<h2 class="text-xl font-bold text-white">群通知</h2>
|
||||||
|
<button class="text-gray-400 hover:text-white transition" title="刷新">
|
||||||
|
<i class="fas fa-sync-alt"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 列表区域 -->
|
||||||
|
<div class="flex-1 overflow-y-auto p-4 custom-scrollbar">
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<div class="text-center py-20 text-gray-600">
|
||||||
|
<i class="fas fa-users-slash text-5xl mb-4 opacity-50"></i>
|
||||||
|
<p class="text-sm">暂无群通知</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 暂时保留为空,后续在此处对接群通知 API
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.custom-scrollbar::-webkit-scrollbar { width: 4px; }
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-thumb { @apply bg-gray-700 rounded-full; }
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user