bug修复
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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[]]
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
Reference in New Issue
Block a user