feat: 消息中心接入部分接口,调整样式
This commit is contained in:
parent
3e1f1fdc67
commit
62143affd5
@ -47,14 +47,18 @@ export interface ChatMessage {
|
|||||||
// 群聊成员接口类型定义
|
// 群聊成员接口类型定义
|
||||||
export interface ChatMember {
|
export interface ChatMember {
|
||||||
id: string
|
id: string
|
||||||
chatId: string
|
phone: string
|
||||||
userId: string
|
sex: number
|
||||||
userName: string
|
avatar: string
|
||||||
userAvatar?: string
|
isTeacher: boolean
|
||||||
role: 'admin' | 'member'
|
email: string
|
||||||
joinTime: string
|
realname: string
|
||||||
|
username: string
|
||||||
|
// 可选字段
|
||||||
|
chatId?: string
|
||||||
|
role?: 'admin' | 'member'
|
||||||
|
joinTime?: string
|
||||||
isOnline?: boolean
|
isOnline?: boolean
|
||||||
// 根据接口文档优化的字段
|
|
||||||
status?: number // 成员状态
|
status?: number // 成员状态
|
||||||
lastActiveTime?: string // 最后活跃时间
|
lastActiveTime?: string // 最后活跃时间
|
||||||
}
|
}
|
||||||
@ -173,5 +177,29 @@ export const ChatApi = {
|
|||||||
*/
|
*/
|
||||||
getUnreadCount: (): Promise<ApiResponse<{ total: number; chats: { chatId: string; count: number }[] }>> => {
|
getUnreadCount: (): Promise<ApiResponse<{ total: number; chats: { chatId: string; count: number }[] }>> => {
|
||||||
return ApiRequest.get('/aiol/aiolChat/unread-count')
|
return ApiRequest.get('/aiol/aiolChat/unread-count')
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开启全员禁言
|
||||||
|
* POST /aiol/aiolChat/{chatId}/mute_all
|
||||||
|
*/
|
||||||
|
muteAll: (chatId: string): Promise<ApiResponse<any>> => {
|
||||||
|
return ApiRequest.post(`/aiol/aiolChat/${chatId}/mute_all`)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭全员禁言
|
||||||
|
* POST /aiol/aiolChat/{chatId}/unmute_all
|
||||||
|
*/
|
||||||
|
unmuteAll: (chatId: string): Promise<ApiResponse<any>> => {
|
||||||
|
return ApiRequest.post(`/aiol/aiolChat/${chatId}/unmute_all`)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询会话详情
|
||||||
|
* GET /aiol/aiolChat/{chatId}/detail
|
||||||
|
*/
|
||||||
|
getChatDetail: (chatId: string): Promise<ApiResponse<any>> => {
|
||||||
|
return ApiRequest.get(`/aiol/aiolChat/${chatId}/detail`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -284,8 +284,8 @@ const handleInput = (value: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
// Ctrl/Cmd + Enter 发送消息
|
// Enter 键发送消息
|
||||||
if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
handleSend()
|
handleSend()
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,6 @@
|
|||||||
{{ contact.name }}
|
{{ contact.name }}
|
||||||
<span v-if="contact.type === 'group'" class="member-count">({{ contact.memberCount || 0 }}人)</span>
|
<span v-if="contact.type === 'group'" class="member-count">({{ contact.memberCount || 0 }}人)</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="contact-time">{{ contact.lastMessageTime }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="contact-preview">
|
<div class="contact-preview">
|
||||||
<span class="last-message">{{ contact.lastMessage }}</span>
|
<span class="last-message">{{ contact.lastMessage }}</span>
|
||||||
@ -170,6 +169,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 禁言状态提示 -->
|
||||||
|
<div v-if="activeContact?.type === 'group' && isAllMuted" class="mute-status-tip">
|
||||||
|
<div class="mute-tip-content">
|
||||||
|
<svg class="mute-icon" viewBox="0 0 24 24" width="14" height="14">
|
||||||
|
<path fill="currentColor"
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" />
|
||||||
|
</svg>
|
||||||
|
<span>群聊已开启全员禁言</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -185,21 +195,40 @@
|
|||||||
<path fill="currentColor"
|
<path fill="currentColor"
|
||||||
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" />
|
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" />
|
||||||
</svg>
|
</svg>
|
||||||
<input type="text" placeholder="搜索群成员" class="search-input">
|
<input type="text" placeholder="搜索群成员" class="search-input" v-model="memberSearchKeyword"
|
||||||
|
@input="handleMemberSearch">
|
||||||
|
<div v-if="isSearchingMembers" class="search-loading">
|
||||||
|
<div class="loading-spinner"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 群成员列表 -->
|
<!-- 群成员列表 -->
|
||||||
<div class="members-section">
|
<div class="members-section">
|
||||||
<div class="members-grid">
|
<!-- 有搜索结果时显示成员列表 -->
|
||||||
<div v-for="i in 20" :key="i" class="member-item">
|
<div v-if="displayedMembers.length > 0" class="members-grid">
|
||||||
|
<div v-for="member in displayedMembers" :key="member.id" class="member-item">
|
||||||
<div class="member-avatar">
|
<div class="member-avatar">
|
||||||
<img src="https://avatars.githubusercontent.com/u/38358644" alt="成员头像" />
|
<img :src="member.avatar || '/images/profile/default-avatar.png'" :alt="member.realname" />
|
||||||
</div>
|
</div>
|
||||||
<div class="member-name">李小多</div>
|
<div class="member-name">{{ member.realname }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="view-more">
|
|
||||||
|
<!-- 搜索无结果时的空状态 -->
|
||||||
|
<div v-else-if="memberSearchKeyword.trim()" class="empty-search-state">
|
||||||
|
<div class="empty-icon">
|
||||||
|
<svg viewBox="0 0 24 24" width="48" height="48">
|
||||||
|
<path fill="currentColor"
|
||||||
|
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="empty-text">未找到相关成员</div>
|
||||||
|
<div class="empty-hint">请尝试其他关键词</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 查看更多按钮 -->
|
||||||
|
<div v-if="shouldShowViewMore" class="view-more" @click="showAllMembers = true">
|
||||||
<span>查看更多</span>
|
<span>查看更多</span>
|
||||||
<svg class="chevron-icon" viewBox="0 0 24 24" width="16" height="16">
|
<svg class="chevron-icon" viewBox="0 0 24 24" width="16" height="16">
|
||||||
<path fill="currentColor" d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z" />
|
<path fill="currentColor" d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z" />
|
||||||
@ -211,11 +240,11 @@
|
|||||||
<div class="info-section">
|
<div class="info-section">
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<span class="info-label">班级名称</span>
|
<span class="info-label">班级名称</span>
|
||||||
<span class="info-value">计算机一班</span>
|
<span class="info-value">{{ groupInfo.name || '暂无' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<span class="info-label">班级人数</span>
|
<span class="info-label">班级人数</span>
|
||||||
<span class="info-value">149人</span>
|
<span class="info-value">{{ groupInfo.memberCount }}人</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -231,8 +260,9 @@
|
|||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<span class="setting-label">全员禁言</span>
|
<span class="setting-label">全员禁言</span>
|
||||||
<div class="switch-wrapper">
|
<div class="switch-wrapper">
|
||||||
<input type="checkbox" id="muteAll" v-model="groupSettings.muteAll" class="switch-input">
|
<input type="checkbox" id="muteAll" v-model="isAllMuted" :disabled="isMuteLoading"
|
||||||
<label for="muteAll" class="switch-label"></label>
|
@change="handleMuteAllToggle" class="switch-input">
|
||||||
|
<label for="muteAll" class="switch-label" :class="{ 'loading': isMuteLoading }"></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
@ -328,6 +358,26 @@ interface Contact {
|
|||||||
unreadCount: number
|
unreadCount: number
|
||||||
isOnline?: boolean
|
isOnline?: boolean
|
||||||
memberCount?: number
|
memberCount?: number
|
||||||
|
izAllMuted?: boolean | number // 全员禁言状态,支持布尔值和数字(1/0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 群聊成员类型定义
|
||||||
|
interface ChatMember {
|
||||||
|
id: string
|
||||||
|
phone: string
|
||||||
|
sex: number
|
||||||
|
avatar: string
|
||||||
|
isTeacher: boolean
|
||||||
|
email: string
|
||||||
|
realname: string
|
||||||
|
username: string
|
||||||
|
// 可选字段
|
||||||
|
chatId?: string
|
||||||
|
role?: 'admin' | 'member'
|
||||||
|
joinTime?: string
|
||||||
|
isOnline?: boolean
|
||||||
|
status?: number // 成员状态
|
||||||
|
lastActiveTime?: string // 最后活跃时间
|
||||||
}
|
}
|
||||||
|
|
||||||
// 消息类型定义(兼容API返回的数据)
|
// 消息类型定义(兼容API返回的数据)
|
||||||
@ -382,6 +432,55 @@ const contacts = ref<Contact[]>([])
|
|||||||
// 当前会话的消息数据
|
// 当前会话的消息数据
|
||||||
const messages = ref<Message[]>([])
|
const messages = ref<Message[]>([])
|
||||||
|
|
||||||
|
// 群成员数据
|
||||||
|
const groupMembers = ref<ChatMember[]>([])
|
||||||
|
|
||||||
|
// 群组信息数据
|
||||||
|
const groupInfo = ref({
|
||||||
|
name: '',
|
||||||
|
memberCount: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 全员禁言状态
|
||||||
|
const isAllMuted = ref(false)
|
||||||
|
const isMuteLoading = ref(false)
|
||||||
|
|
||||||
|
// 群成员显示控制
|
||||||
|
const showAllMembers = ref(false)
|
||||||
|
const maxDisplayMembers = 20 // 5行 × 4列 = 20个成员
|
||||||
|
|
||||||
|
// 群成员搜索
|
||||||
|
const memberSearchKeyword = ref('')
|
||||||
|
const isSearchingMembers = ref(false)
|
||||||
|
|
||||||
|
// 计算属性:过滤后的群成员列表
|
||||||
|
const filteredMembers = computed(() => {
|
||||||
|
if (!memberSearchKeyword.value.trim()) {
|
||||||
|
return groupMembers.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyword = memberSearchKeyword.value.toLowerCase().trim()
|
||||||
|
return groupMembers.value.filter((member: any) =>
|
||||||
|
member.realname.toLowerCase().includes(keyword) ||
|
||||||
|
member.username.toLowerCase().includes(keyword)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算属性:显示的群成员列表
|
||||||
|
const displayedMembers = computed(() => {
|
||||||
|
const members = filteredMembers.value
|
||||||
|
|
||||||
|
if (showAllMembers.value || members.length <= maxDisplayMembers) {
|
||||||
|
return members
|
||||||
|
}
|
||||||
|
return members.slice(0, maxDisplayMembers)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算属性:是否需要显示"查看更多"按钮
|
||||||
|
const shouldShowViewMore = computed(() => {
|
||||||
|
return filteredMembers.value.length > maxDisplayMembers && !showAllMembers.value
|
||||||
|
})
|
||||||
|
|
||||||
// 生命周期钩子
|
// 生命周期钩子
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadContacts()
|
loadContacts()
|
||||||
@ -407,7 +506,8 @@ const loadContacts = async () => {
|
|||||||
lastMessageTime: formatTime(chat.lastMessageTime || chat.updateTime),
|
lastMessageTime: formatTime(chat.lastMessageTime || chat.updateTime),
|
||||||
unreadCount: chat.unreadCount || 0,
|
unreadCount: chat.unreadCount || 0,
|
||||||
isOnline: chat.isOnline,
|
isOnline: chat.isOnline,
|
||||||
memberCount: chat.memberCount || (contactType === 'group' ? 0 : undefined)
|
memberCount: chat.memberCount || (contactType === 'group' ? 0 : undefined),
|
||||||
|
izAllMuted: chat.izAllMuted === 1
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -446,6 +546,112 @@ const loadGroupMemberCount = async (chatId: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载群成员列表
|
||||||
|
const loadGroupMembers = async (chatId: string) => {
|
||||||
|
try {
|
||||||
|
const response = await ChatApi.getChatMembers(chatId)
|
||||||
|
|
||||||
|
if (response.data && response.data.success) {
|
||||||
|
groupMembers.value = response.data.result || []
|
||||||
|
console.log('群成员加载成功:', groupMembers.value.length, '个成员')
|
||||||
|
|
||||||
|
// 从联系人数据中获取群组名称和禁言状态
|
||||||
|
const contact = contacts.value.find((c: Contact) => c.id === chatId)
|
||||||
|
const groupName = contact ? contact.name : '暂无'
|
||||||
|
|
||||||
|
// 更新群组信息
|
||||||
|
groupInfo.value = {
|
||||||
|
name: groupName,
|
||||||
|
memberCount: groupMembers.value.length
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
console.log('API响应失败:', response.data)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取群成员失败:', error)
|
||||||
|
message.error('获取群成员失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理群成员搜索
|
||||||
|
const handleMemberSearch = () => {
|
||||||
|
isSearchingMembers.value = true
|
||||||
|
|
||||||
|
// 模拟搜索延迟
|
||||||
|
setTimeout(() => {
|
||||||
|
isSearchingMembers.value = false
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取群聊详情(包括禁言状态)
|
||||||
|
const loadChatDetail = async (chatId: string) => {
|
||||||
|
try {
|
||||||
|
const response = await ChatApi.getChatDetail(chatId)
|
||||||
|
|
||||||
|
if (response.data && response.data.success) {
|
||||||
|
const chatDetail = response.data.result
|
||||||
|
|
||||||
|
// 更新禁言状态
|
||||||
|
if (chatDetail.izAllMuted !== undefined) {
|
||||||
|
isAllMuted.value = chatDetail.izAllMuted === 1
|
||||||
|
} else {
|
||||||
|
// 备用方案:从联系人数据中获取禁言状态
|
||||||
|
const contact = contacts.value.find((c: Contact) => c.id === chatId)
|
||||||
|
if (contact && contact.izAllMuted !== undefined) {
|
||||||
|
isAllMuted.value = contact.izAllMuted === 1 || contact.izAllMuted === true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return chatDetail
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取群聊详情失败:', error)
|
||||||
|
|
||||||
|
// 如果API调用失败,尝试从联系人数据中获取状态
|
||||||
|
const contact = contacts.value.find((c: Contact) => c.id === chatId)
|
||||||
|
if (contact && contact.izAllMuted !== undefined) {
|
||||||
|
isAllMuted.value = contact.izAllMuted === 1 || contact.izAllMuted === true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理全员禁言切换
|
||||||
|
const handleMuteAllToggle = async () => {
|
||||||
|
if (!activeContactId.value) return
|
||||||
|
|
||||||
|
isMuteLoading.value = true
|
||||||
|
|
||||||
|
// 保存当前状态,用于错误回滚
|
||||||
|
const previousState = isAllMuted.value
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (isAllMuted.value) {
|
||||||
|
// 开启全员禁言
|
||||||
|
await ChatApi.muteAll(activeContactId.value)
|
||||||
|
message.success('已开启全员禁言')
|
||||||
|
} else {
|
||||||
|
// 关闭全员禁言
|
||||||
|
await ChatApi.unmuteAll(activeContactId.value)
|
||||||
|
message.success('已关闭全员禁言')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新联系人列表中的状态
|
||||||
|
const contact = contacts.value.find((c: Contact) => c.id === activeContactId.value)
|
||||||
|
if (contact) {
|
||||||
|
contact.izAllMuted = isAllMuted.value ? 1 : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('切换全员禁言状态失败:', error)
|
||||||
|
message.error('操作失败,请重试')
|
||||||
|
// 回滚状态
|
||||||
|
isAllMuted.value = previousState
|
||||||
|
} finally {
|
||||||
|
isMuteLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 加载指定会话的消息
|
// 加载指定会话的消息
|
||||||
const loadMessages = async (chatId: string) => {
|
const loadMessages = async (chatId: string) => {
|
||||||
messagesLoading.value = true
|
messagesLoading.value = true
|
||||||
@ -538,6 +744,16 @@ const currentMessages = computed(() => {
|
|||||||
const selectContact = async (contactId: string) => {
|
const selectContact = async (contactId: string) => {
|
||||||
activeContactId.value = contactId
|
activeContactId.value = contactId
|
||||||
|
|
||||||
|
// 重置群成员展开状态、群组信息和搜索状态
|
||||||
|
showAllMembers.value = false
|
||||||
|
memberSearchKeyword.value = ''
|
||||||
|
isSearchingMembers.value = false
|
||||||
|
isMuteLoading.value = false
|
||||||
|
groupInfo.value = {
|
||||||
|
name: '',
|
||||||
|
memberCount: 0
|
||||||
|
}
|
||||||
|
|
||||||
// 清除未读数量
|
// 清除未读数量
|
||||||
const contact = contacts.value.find((c: Contact) => c.id === contactId)
|
const contact = contacts.value.find((c: Contact) => c.id === contactId)
|
||||||
if (contact) {
|
if (contact) {
|
||||||
@ -547,17 +763,23 @@ const selectContact = async (contactId: string) => {
|
|||||||
if (contact.type === 'group' && (!contact.memberCount || contact.memberCount === 0)) {
|
if (contact.type === 'group' && (!contact.memberCount || contact.memberCount === 0)) {
|
||||||
loadGroupMemberCount(contactId)
|
loadGroupMemberCount(contactId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果是群聊,加载群成员列表和群聊详情
|
||||||
|
if (contact.type === 'group') {
|
||||||
|
loadGroupMembers(contactId)
|
||||||
|
loadChatDetail(contactId) // 获取群聊详情,包括禁言状态
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载该会话的消息
|
// 加载该会话的消息
|
||||||
await loadMessages(contactId)
|
await loadMessages(contactId)
|
||||||
|
|
||||||
// 标记消息为已读
|
// 暂时移除标记消息已读的API调用,避免错误
|
||||||
try {
|
// try {
|
||||||
await ChatApi.markAsRead(contactId)
|
// await ChatApi.markAsRead(contactId)
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.warn('标记消息已读失败:', error)
|
// console.warn('标记消息已读失败:', error)
|
||||||
}
|
// }
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
@ -1011,7 +1233,6 @@ onMounted(() => {
|
|||||||
|
|
||||||
.contact-header {
|
.contact-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
color: #3F3F44;
|
color: #3F3F44;
|
||||||
@ -1033,11 +1254,6 @@ onMounted(() => {
|
|||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact-time {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #999;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contact-preview {
|
.contact-preview {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -1321,6 +1537,33 @@ onMounted(() => {
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 禁言状态提示样式 */
|
||||||
|
.mute-status-tip {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 12px 0 8px 0;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mute-tip-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
background: #e0e6ec;
|
||||||
|
color: #6c757d;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 30px;
|
||||||
|
font-size: 12px;
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
max-width: 300px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mute-icon {
|
||||||
|
color: #6c757d;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.image-bubble {
|
.image-bubble {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@ -1473,6 +1716,62 @@ onMounted(() => {
|
|||||||
border-color: #1890ff;
|
border-color: #1890ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-loading {
|
||||||
|
position: absolute;
|
||||||
|
right: 12px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border: 2px solid #f3f3f3;
|
||||||
|
border-top: 2px solid #1890ff;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 空状态样式 */
|
||||||
|
.empty-search-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
color: #d9d9d9;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-hint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
.members-section {
|
.members-section {
|
||||||
margin: 0 20px;
|
margin: 0 20px;
|
||||||
border-bottom: 1.5px solid #E6E6E6;
|
border-bottom: 1.5px solid #E6E6E6;
|
||||||
@ -1526,20 +1825,28 @@ onMounted(() => {
|
|||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
transition: color 0.2s ease;
|
margin-top: 8px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: #f8f8f8;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-more:hover {
|
.view-more:hover {
|
||||||
color: #1890ff;
|
color: #1890ff;
|
||||||
|
background: #f0f8ff;
|
||||||
|
border-color: #1890ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chevron-icon {
|
.chevron-icon {
|
||||||
color: #666666;
|
color: #666666;
|
||||||
transition: color 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-more:hover .chevron-icon {
|
.view-more:hover .chevron-icon {
|
||||||
color: #1890ff;
|
color: #1890ff;
|
||||||
|
transform: translateY(1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 私聊详情样式 */
|
/* 私聊详情样式 */
|
||||||
@ -1670,6 +1977,24 @@ onMounted(() => {
|
|||||||
transform: translateX(20px);
|
transform: translateX(20px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.switch-label.loading {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-label.loading:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
margin: -6px 0 0 -6px;
|
||||||
|
border: 2px solid #f3f3f3;
|
||||||
|
border-top: 2px solid #0288D1;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
.actions-section {
|
.actions-section {
|
||||||
margin: 0 20px;
|
margin: 0 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user