bug修复

This commit is contained in:
2025-12-08 14:56:02 +08:00
parent 3a31ea1ba6
commit 11f2e8780e
6 changed files with 298 additions and 46 deletions

View File

@@ -648,6 +648,16 @@ async function saveAnnouncement() {
watch(() => props.roomId, loadData, { immediate: true })
// 监听群通知,自动刷新成员列表
watch(() => chatStore.lastGroupNotification, (notif) => {
if (notif && notif.room_id === props.roomId) {
// 延迟刷新,确保后端数据已更新
setTimeout(() => {
loadData()
}, 300)
}
}, { deep: true })
onMounted(() => {
if (chatStore.contacts.length === 0) {
contactApi.getContacts().then(res => chatStore.setContacts(res))

View File

@@ -15,32 +15,49 @@
<p class="text-gray-400 text-xs">无法在此群聊中发送消息</p>
</div>
</div>
<!-- 遮罩层:禁言状态 -->
<div
v-else-if="isMuted"
class="absolute inset-0 bg-black/80 backdrop-blur-sm z-50 flex items-center justify-center cursor-not-allowed"
@click.stop
@contextmenu.prevent
@mousedown.prevent
@selectstart.prevent
>
<div class="text-center px-6">
<i class="fas fa-volume-mute text-4xl text-orange-500 mb-3"></i>
<p class="text-white font-semibold text-sm mb-1">您已被禁言</p>
<p v-if="muteCountdown" class="text-orange-400 text-sm font-mono">{{ muteCountdown }}</p>
<p v-else class="text-gray-400 text-xs">无法在此群聊中发送消息</p>
</div>
</div>
<div class="flex gap-6 text-gray-400 text-lg mb-3 px-2 items-center">
<i
class="far fa-smile hover:text-primary transition-all transform hover:scale-125 hover:rotate-12"
:class="isBlocked ? 'text-gray-600 cursor-not-allowed' : 'text-gray-400 cursor-pointer'"
:class="cannotSend ? 'text-gray-600 cursor-not-allowed' : 'text-gray-400 cursor-pointer'"
title="表情"
@click="!isBlocked && insertEmoji('😊')"
@click="!cannotSend && insertEmoji('😊')"
></i>
<i
class="far fa-image hover:text-green-400 transition-all transform hover:scale-125"
:class="isBlocked ? 'text-gray-600 cursor-not-allowed' : 'text-gray-400 cursor-pointer'"
@click="!isBlocked && triggerFile(1)"
:class="cannotSend ? 'text-gray-600 cursor-not-allowed' : 'text-gray-400 cursor-pointer'"
@click="!cannotSend && triggerFile(1)"
title="发送图片"
></i>
<!-- 新增:视频上传按钮 -->
<i
class="fas fa-video hover:text-purple-400 transition-all transform hover:scale-125"
:class="isBlocked ? 'text-gray-600 cursor-not-allowed' : 'text-gray-400 cursor-pointer'"
@click="!isBlocked && triggerFile(3)"
:class="cannotSend ? 'text-gray-600 cursor-not-allowed' : 'text-gray-400 cursor-pointer'"
@click="!cannotSend && triggerFile(3)"
title="发送视频"
></i>
<i
class="fas fa-folder-plus hover:text-yellow-400 transition-all transform hover:scale-125"
:class="isBlocked ? 'text-gray-600 cursor-not-allowed' : 'text-gray-400 cursor-pointer'"
@click="!isBlocked && triggerFile(8)"
:class="cannotSend ? 'text-gray-600 cursor-not-allowed' : 'text-gray-400 cursor-pointer'"
@click="!cannotSend && triggerFile(8)"
title="发送文件"
></i>
@@ -48,20 +65,20 @@
<i
v-if="isGroupChat && mentionUsers.length > 0"
class="fas fa-at hover:text-blue-400 transition-all transform hover:scale-125"
:class="isBlocked ? 'text-gray-600 cursor-not-allowed' : 'text-gray-400 cursor-pointer'"
@click="!isBlocked && showMentionPickerManual()"
:class="cannotSend ? 'text-gray-600 cursor-not-allowed' : 'text-gray-400 cursor-pointer'"
@click="!cannotSend && showMentionPickerManual()"
title="@群成员"
></i>
<!-- 长按录音 -->
<div
class="relative flex items-center group select-none ml-auto mr-2"
:class="isBlocked ? 'cursor-not-allowed' : 'cursor-pointer'"
@mousedown="!isBlocked && handleRecordStart()"
@touchstart.passive="!isBlocked && handleRecordStart()"
@mouseup="!isBlocked && handleRecordStop()"
@touchend.prevent="!isBlocked && handleRecordStop()"
@mouseleave="!isBlocked && handleRecordCancel()"
:class="cannotSend ? 'cursor-not-allowed' : 'cursor-pointer'"
@mousedown="!cannotSend && handleRecordStart()"
@touchstart.passive="!cannotSend && handleRecordStart()"
@mouseup="!cannotSend && handleRecordStop()"
@touchend.prevent="!cannotSend && handleRecordStop()"
@mouseleave="!cannotSend && handleRecordCancel()"
>
<div
class="w-8 h-8 rounded-full flex items-center justify-center transition-all shadow-md"
@@ -83,7 +100,7 @@
<textarea
ref="textareaRef"
:value="modelValue"
:disabled="isBlocked"
:disabled="cannotSend"
class="w-full bg-transparent border-none text-white resize-none h-10 max-h-32 py-2 px-1 text-sm focus:outline-none scrollbar-thin placeholder-gray-500 leading-6 disabled:opacity-50 disabled:cursor-not-allowed"
placeholder="输入消息 (Enter 发送)..."
@input="handleInput"
@@ -92,7 +109,7 @@
></textarea>
<div
class="w-10 h-10 rounded-2xl flex items-center justify-center ml-2 transition-all duration-300 shadow-lg"
:class="isBlocked
:class="cannotSend
? 'bg-gray-700 text-gray-500 scale-90 rotate-90 opacity-50 cursor-not-allowed'
: (modelValue.trim() ? 'bg-primary hover:bg-indigo-500 text-white scale-100 rotate-0 cursor-pointer' : 'bg-gray-700 text-gray-500 scale-90 rotate-90 opacity-50 cursor-not-allowed')"
@click="handleSend"
@@ -142,13 +159,81 @@ const toastStore = useToastStore()
interface Props {
modelValue: string
isRecording: boolean
isBlocked?: boolean // 是否被禁言(被移除或退出群聊)
isBlocked?: boolean // 是否被移除或退出群聊
isMuted?: boolean // 是否被禁言
mutedUntil?: string | null // 禁言到期时间
isGroupChat?: boolean // 是否是群聊
roomMembers?: Record<string, { name: string; avatar?: string }> // 群成员映射
}
const props = defineProps<Props>()
// 禁言倒计时
const muteCountdown = ref('')
let muteTimer: ReturnType<typeof setInterval> | null = null
// 计算是否可以发送消息(被移除或被禁言都不能发)
const cannotSend = computed(() => props.isBlocked || props.isMuted)
// 更新禁言倒计时
function updateMuteCountdown() {
if (!props.mutedUntil) {
muteCountdown.value = ''
return
}
const now = new Date().getTime()
const end = new Date(props.mutedUntil).getTime()
const diff = end - now
if (diff <= 0) {
muteCountdown.value = ''
if (muteTimer) {
clearInterval(muteTimer)
muteTimer = null
}
return
}
// 计算剩余时间
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60))
const seconds = Math.floor((diff % (1000 * 60)) / 1000)
if (days > 0) {
muteCountdown.value = `${days}天${hours}小时后解除`
} else if (hours > 0) {
muteCountdown.value = `${hours}小时${minutes}分钟后解除`
} else if (minutes > 0) {
muteCountdown.value = `${minutes}分${seconds}秒后解除`
} else {
muteCountdown.value = `${seconds}秒后解除`
}
}
// 监听禁言状态变化
watch(() => props.mutedUntil, (newVal) => {
if (muteTimer) {
clearInterval(muteTimer)
muteTimer = null
}
if (newVal && props.isMuted) {
updateMuteCountdown()
muteTimer = setInterval(updateMuteCountdown, 1000)
} else {
muteCountdown.value = ''
}
}, { immediate: true })
onUnmounted(() => {
if (muteTimer) {
clearInterval(muteTimer)
muteTimer = null
}
})
const emit = defineEmits<{
'update:modelValue': [value: string]
send: [mentionUserIds?: string[]]

View File

@@ -2,7 +2,7 @@
<div
class="flex items-center justify-center text-white font-bold shadow-md transition overflow-hidden"
:class="[sizeClass, roundedClass, { 'cursor-pointer hover:opacity-80': $attrs.onClick }]"
:style="isImage ? {} : { background: color || generateColor(name || avatar || '') }"
:style="customSizeStyle"
@click="$emit('click')"
>
<img
@@ -24,7 +24,7 @@ interface Props {
avatar?: string
name?: string
color?: string
size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'contact'
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'contact' | number
rounded?: 'full' | 'xl' | 'lg'
}
@@ -64,16 +64,43 @@ const isImage = computed(() => {
return false
})
// 预定义尺寸映射
const sizeMap: Record<string, string> = {
xs: 'w-7 h-7 text-[10px]',
sm: 'w-9 h-9 text-xs',
md: 'w-10 h-10 text-sm',
lg: 'w-12 h-12 text-base',
xl: 'w-16 h-16 text-xl',
'2xl': 'w-24 h-24 text-2xl',
contact: 'w-11 h-11 text-lg',
}
// 尺寸 class(仅用于预定义尺寸)
const sizeClass = computed(() => {
const sizes = {
sm: 'w-9 h-9 text-xs',
md: 'w-10 h-10 text-sm',
lg: 'w-12 h-12 text-base',
xl: 'w-16 h-16 text-xl',
'2xl': 'w-24 h-24 text-2xl',
contact: 'w-11 h-11 text-lg',
if (typeof props.size === 'number') {
return '' // 数字尺寸使用 style
}
return sizes[props.size]
return sizeMap[props.size] || sizeMap.md
})
// 自定义尺寸样式(用于数字尺寸和背景色)
const customSizeStyle = computed(() => {
const style: Record<string, string> = {}
// 背景色
if (!isImage.value) {
style.background = props.color || generateColor(props.name || props.avatar || '')
}
// 数字尺寸
if (typeof props.size === 'number') {
style.width = `${props.size}px`
style.height = `${props.size}px`
// 根据尺寸计算字体大小
style.fontSize = `${Math.max(10, props.size * 0.4)}px`
}
return style
})
const roundedClass = computed(() => {