﻿<template>
  <el-dialog
    v-model="visible"
    title="消息"
    width="60%"
    :close-on-click-modal="false"
    class="chat-dialog custom-dialog"
    @opened="onDialogOpen"
    @closed="onDialogClose"
  >
    <div class="chat-container">
      <!-- 左侧：通讯录 + 最近聊天 -->
      <div class="chat-sidebar">
        <div class="sidebar-tabs">
          <div class="sidebar-tab" :class="{ active: sidebarTab === 'recent' }" @click="sidebarTab = 'recent'">
            <el-icon><ChatLineRound /></el-icon>
            <span>最近</span>
          </div>
          <div class="sidebar-tab" :class="{ active: sidebarTab === 'contacts' }" @click="sidebarTab = 'contacts'">
            <el-icon><User /></el-icon>
            <span>通讯录</span>
          </div>
        </div>
        <div class="sidebar-search">
          <el-input v-model="searchKeyword" placeholder="搜索用户" clearable>
            <template #prefix><el-icon><Search /></el-icon></template>
          </el-input>
        </div>

        <!-- 最近聊天列表 -->
        <div v-if="sidebarTab === 'recent'" class="sidebar-list">
          <div v-for="chat in recentChats" :key="chat.id" class="sidebar-item" :class="{ active: currentContact?.id === chat.id }" @click="selectContact(chat)">
            <el-badge :value="chat.unread || 0" :hidden="!chat.unread" :max="99">
              <el-avatar :size="36" :src="chat.avatar || undefined" :style="{ background: chat.avatar ? 'transparent' : chat.color }">
                {{ chat.name?.[0] || '?' }}
              </el-avatar>
            </el-badge>
            <div class="sidebar-item-info">
              <div class="sidebar-item-name">{{ chat.name }}</div>
              <div class="sidebar-item-msg">{{ chat.lastMsg || '暂无消息' }}</div>
            </div>
            <el-tooltip content="删除会话" placement="top">
              <el-button link type="danger" @click.stop="deleteRecentChat(chat)">
                <el-icon><Delete /></el-icon>
              </el-button>
            </el-tooltip>
          </div>
          <div v-if="recentChats.length === 0" class="sidebar-empty">暂无最近聊天</div>
        </div>

        <!-- 通讯录列表 -->
        <div v-else class="sidebar-list">
          <div v-for="contact in filteredContacts" :key="contact.id" class="sidebar-item" :class="{ active: currentContact?.id === contact.id }" @click="selectContact(contact)">
            <el-avatar :size="36" :src="contact.avatar || undefined" :style="{ background: contact.avatar ? 'transparent' : contact.color }">
              {{ contact.name?.[0] || '?' }}
            </el-avatar>
            <div class="sidebar-item-info">
              <div class="sidebar-item-name">{{ contact.nickname || contact.username }}</div>
              <div class="sidebar-item-status" :class="{ online: contact.online }">
                {{ contact.online ? '在线' : '离线' }}
              </div>
            </div>
          </div>
          <div v-if="filteredContacts.length === 0" class="sidebar-empty">暂无联系人</div>
        </div>
      </div>

      <!-- 右侧聊天区域 -->
      <div class="chat-main" v-if="currentContact">
        <div class="chat-header">
          <el-avatar :size="32" :src="currentContact.avatar || undefined" :style="{ background: currentContact.avatar ? 'transparent' : currentContact.color }">
            {{ currentContact.name?.[0] || currentContact.username?.[0] || '?' }}
          </el-avatar>
          <span class="chat-title">{{ currentContact.nickname || currentContact.username || currentContact.name }}</span>
          <span class="chat-status" :class="{ online: currentContact.online }">
            {{ currentContact.online ? '在线' : '离线' }}
          </span>
        </div>

        <!-- 消息列表 -->
        <div class="chat-messages" ref="messagesRef">
          <div v-if="loadingMessages" class="messages-loading">
            <el-icon class="is-loading"><Loading /></el-icon> 加载中..
          </div>
          <template v-else>
            <div v-for="msg in currentMessages" :key="msg.id" class="message-wrapper" :class="{ self: msg.isSelf }">
              <el-popover placement="left" :width="200" trigger="hover">
                <template #reference>
                  <el-avatar
                    :size="30"
                    :src="msg.isSelf ? (userStore.avatar || undefined) : (msg.fromAvatar || undefined)"
                    :style="{ background: msg.isSelf ? (userStore.avatar ? 'transparent' : '#409eff') : (msg.fromAvatar ? 'transparent' : (msg.fromColor || '#909399')) }"
                    class="msg-avatar"
                  >
                    {{ (msg.isSelf ? currentUserName : (msg.fromNickname || msg.fromUsername))?.[0] || '?' }}
                  </el-avatar>
                </template>
                <div class="user-info-popover">
                  <div class="user-info-row">
                    <span class="user-info-label">账号:</span>
                    <span class="user-info-value">{{ msg.isSelf ? currentUserName : (msg.fromUsername || '未知') }}</span>
                  </div>
                  <div class="user-info-row">
                    <span class="user-info-label">昵称:</span>
                    <span class="user-info-value">{{ msg.isSelf ? (currentUserNickname || currentUserName) : (msg.fromNickname || '未知') }}</span>
                  </div>
                  <div class="user-info-row signature-row">
                    <span class="user-info-label">签名:</span>
                    <div class="user-info-signature">
                      {{ msg.isSelf ? (userStore.signature || '暂无') : (msg.fromSignature || '暂无') }}
                    </div>
                  </div>
                </div>
              </el-popover>
              <div class="message-content">
                <div class="message-info">
                  <span class="sender-name">{{ msg.isSelf ? '' : (msg.fromNickname || msg.fromUsername) }}</span>
                  <span class="message-time">{{ formatTime(msg.createTime) }}</span>
                </div>

                <!-- 发送中 -->
                <div v-if="msg.sending" class="message-bubble message-sending">
                  <template v-if="msg.tempType === 'image'">
                    <div class="uploading-mini">
                      <el-icon class="is-loading"><Loading /></el-icon>
                      <span>发送中</span>
                    </div>
                  </template>
                  <template v-else-if="msg.tempType === 'file'">
                    <div class="uploading-mini">
                      <el-icon class="is-loading"><Loading /></el-icon>
                      <span>发送中</span>
                    </div>
                  </template>
                  <template v-else>{{ msg.content }}</template>
                </div>

                <!-- 发送失败 -->
                <div v-else-if="msg.failed" class="message-bubble message-failed" @click="retrySend(msg)">
                  <template v-if="msg.tempType === 'image'">
                    <div>图片发送失败，点击重试</div>
                  </template>
                  <template v-else-if="msg.tempType === 'file'">
                    <div>文件发送失败，点击重试</div>
                  </template>
                  <template v-else>{{ msg.content }} <span class="failed-hint">(发送失败，点击重试)</span></template>
                </div>

                <!-- 已发送 -->
                <template v-else>
                  <div v-if="msg.type === 'text' || !msg.type" class="message-bubble">{{ msg.content }}</div>
                  <div v-else-if="msg.type === 'image'" class="message-image">
                    <el-image :src="getMediaUrl(msg.content)" fit="cover" :preview-src-list="[getMediaUrl(msg.content)]" :preview-teleported="true" />
                  </div>
                  <div v-else-if="msg.type === 'file'" class="message-file">
                    <div class="file-icon" :style="{ background: getFileColor(msg.fileName) }">
                      <span>{{ getFileExt(msg.fileName) }}</span>
                    </div>
                    <div class="file-info">
                      <div class="file-name" :title="msg.fileName">{{ msg.fileName || '文件' }}</div>
                      <div class="file-size">{{ formatSize(msg.fileSize) }}</div>
                    </div>
                    <el-button link @click="downloadFile(msg)">
                      <el-icon><Download /></el-icon>
                    </el-button>
                  </div>
                </template>
              </div>
            </div>
          </template>
        </div>

        <!-- 工具栏 -->
        <div class="chat-toolbar">
          <el-tooltip content="发送图片">
            <el-button link :disabled="uploading" @click="triggerImageUpload">
              <el-icon :size="18"><Picture /></el-icon>
            </el-button>
          </el-tooltip>
          <el-tooltip content="发送文件">
            <el-button link :disabled="uploading" @click="triggerFileUpload">
              <el-icon :size="18"><Paperclip /></el-icon>
            </el-button>
          </el-tooltip>
          <el-tooltip content="表情">
            <el-popover placement="top" :width="440" :height="280" trigger="click" v-model:visible="emojiVisible">
              <template #reference>
                <el-button link style="font-size:18px">😊</el-button>
              </template>
              <div class="emoji-panel">
                <span v-for="emoji in emojis" :key="emoji" class="emoji-item" @click="sendEmoji(emoji)">{{ emoji }}</span>
              </div>
            </el-popover>
          </el-tooltip>
        </div>

        <!-- 输入框 -->
        <div class="chat-input-area">
          <el-input v-model="inputText" type="textarea" :rows="4" placeholder="输入消息，Enter 发送" resize="none" @keydown.enter.exact.prevent="sendMessage" />
          <el-button type="primary" @click="sendMessage" class="send-btn"><el-icon><Promotion /></el-icon>发送</el-button>
        </div>
      </div>

      <!-- 空状态 -->
      <div v-else class="chat-empty">
        <el-icon :size="48"><ChatDotRound /></el-icon>
        <p>选择联系人开始聊天</p>
      </div>
    </div>

    <!-- 隐藏的文件选择器 -->
    <input ref="imageInputRef" type="file" accept="image/*" style="display:none" @change="handleImageSelect" />
    <input ref="fileInputRef" type="file" style="display:none" @change="handleFileSelect" />
  </el-dialog>
</template>

<script setup>
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import { ChatLineRound, User, Search, Loading, Picture, Paperclip, Download, ChatDotRound, Promotion, Delete } from '@element-plus/icons-vue'
import { useUserStore } from '@/store/user'
import { getUsers, getMessages, sendMessage as sendMessageApi, uploadChatFile, getUnreadList, deleteConversation } from '@/api/message'
import { chatService } from '@/services/chat'

const props = defineProps({ modelValue: Boolean })
const emit = defineEmits(['update:modelValue', 'unread-change'])
const userStore = useUserStore()

const visible = computed({
  get: () => props.modelValue,
  set: (val) => emit('update:modelValue', val)
})

const sidebarTab = ref('recent')
const searchKeyword = ref('')
const contacts = ref([])
const recentChats = ref([])
const messages = ref({})
const onlineUsers = ref(new Set())
const currentUserName = ref('')
const currentUserNickname = ref('')
const currentContact = ref(null)
const inputText = ref('')
const loadingMessages = ref(false)
const emojiVisible = ref(false)
const messagesRef = ref(null)
const imageInputRef = ref(null)
const fileInputRef = ref(null)
const uploading = ref(false)
let unsubscribeWs = null
let tempMsgId = 0

const emojis = ['😀','😂','😍','🥰','😎','🤔','😅','😭','🎉','👍','❤️','🔥','😱','🙏','💪','😘','😜','😊','😁','😃','😄','😆','😉','😌','😋','😛','😲','😞','😠','😡','😢','😤','😥','😦','😧','😨','😩','😪','😫','😬','😮','😯','😰','😳','😴','😵','😶','😷','😸','😹','😺','😻','😼','😽','😾','😿','🙀','🙁','🙂','🙃','🙄']
const colors = ['#f56c6c', '#e6a23c', '#67c23a', '#409eff', '#909399', '#c71585', '#00bcd4']

const currentMessages = computed(() => {
  if (!currentContact.value) return []
  return messages.value[currentContact.value.id] || []
})

const filteredContacts = computed(() => {
  if (!searchKeyword.value) return contacts.value
  const kw = searchKeyword.value.toLowerCase()
  return contacts.value.filter(c =>
    (c.username || '').toLowerCase().includes(kw) ||
    (c.nickname || '').toLowerCase().includes(kw)
  )
})

// ======== 工具函数 ========

const getMediaUrl = (url) => url || ''
const formatTime = (date) => {
  if (!date) return ''
  const d = new Date(date)
  const now = new Date()
  const diff = now - d
  if (diff < 60000) return '刚刚'
  if (diff < 3600000) return Math.floor(diff / 60000) + '分钟前'
  if (diff < 86400000) return Math.floor(diff / 3600000) + '小时前'
  return d.toLocaleDateString('zh-CN')
}

const formatSize = (size) => {
  if (!size) return '0 B'
  const units = ['B', 'KB', 'MB', 'GB']
  let i = 0
  while (size >= 1024 && i < units.length - 1) { size /= 1024; i++ }
  return size.toFixed(2) + ' ' + units[i]
}

const getFileExt = (name) => {
  if (!name) return '?'
  const ext = name.includes('.') ? name.split('.').pop().toUpperCase().slice(0, 4) : '?'
  return ext
}

const getFileColor = (name) => {
  if (!name) return '#909399'
  const ext = name.split('.').pop().toLowerCase()
  const colorMap = {
    pdf: '#e74c3c', doc: '#2980b9', docx: '#2980b9', xls: '#27ae60', xlsx: '#27ae60',
    ppt: '#e67e22', pptx: '#e67e22', zip: '#8e44ad', rar: '#8e44ad', '7z': '#8e44ad',
    txt: '#95a5a6', md: '#95a5a6', csv: '#27ae60', mp3: '#e91e63', mp4: '#9c27b0',
    avi: '#9c27b0', mov: '#9c27b0', jpg: '#00bcd4', jpeg: '#00bcd4', png: '#00bcd4',
    gif: '#00bcd4', svg: '#00bcd4', webp: '#00bcd4', psd: '#1565c0', ai: '#f57c00'
  }
  return colorMap[ext] || '#909399'
}

// ======== 联系人 & 消息加载 ========

const loadContacts = async () => {
  try {
    const res = await getUsers()
    contacts.value = (res.data || []).map((u, i) => ({
      ...u,
      id: u.id,
      name: u.nickname || u.username,
      color: colors[i % colors.length],
      online: onlineUsers.value.has(Number(u.id)),
      unread: 0,
      lastMsg: ''
    }))
  } catch (e) {
    ElMessage.error('加载联系人失败')
  }
}

const loadUnreadChats = async () => {
  try {
    const res = await getUnreadList()
    const list = res.data || []
    if (list.length === 0) return
    // 加载联系人后再填充未读数据
    list.forEach(item => {
      const contact = contacts.value.find(c => Number(c.id) === Number(item.userId))
      if (!contact) {
        // 联系人列表中找不到，用未读信息创建
        const i = recentChats.value.length
        const newContact = {
          id: item.userId,
          username: item.username || '',
          nickname: item.nickname || item.username || '',
          avatar: item.avatar || '',
          signature: item.signature || '',
          name: item.nickname || item.username || '',
          color: colors[i % colors.length],
          online: false,
          unread: item.unread || 0,
          lastMsg: item.lastMsg || ''
        }
        recentChats.value.push(newContact)
      } else {
        // 已在联系人列表中，更新最近聊天
        const chat = recentChats.value.find(c => c.id === contact.id)
        if (chat) {
          chat.unread = item.unread || 0
          chat.lastMsg = item.lastMsg || chat.lastMsg
        } else {
          contact.unread = item.unread || 0
          contact.lastMsg = item.lastMsg || ''
          recentChats.value.push({ ...contact })
        }
      }
    })
  } catch (e) {
    // ignore
  }
}

const selectContact = async (contact) => {
  currentContact.value = contact
  contact.unread = 0
  loadingMessages.value = true
  try {
    const res = await getMessages({ userId: contact.id })
    messages.value[contact.id] = (res.data || []).map(msg => {
      const isSelf = String(msg.fromUserId) === String(userStore.userId)
      return {
        ...msg,
        isSelf,
        sending: false,
        failed: false,
        fromColor: colors[Math.abs(Number(isSelf ? userStore.userId : msg.fromUserId) % colors.length)]
      }
    })
    updateRecentChat(contact, '')
  } catch (e) {
    ElMessage.error('加载消息失败')
  } finally {
    loadingMessages.value = false
    scrollToBottom()
  }
}

const updateRecentChat = (contact, lastMsg) => {
  let chat = recentChats.value.find(c => c.id === contact.id)
  if (!chat) {
    chat = { ...contact }
    recentChats.value.unshift(chat)
  }
  if (lastMsg) chat.lastMsg = lastMsg
  chat.unread = 0
}

// ======== 发送消息 ========

const sendMessage = () => {
  const text = inputText.value.trim()
  if (!text || !currentContact.value) return
  inputText.value = ''

  // 文本消息不创建临时消息，直接通过 WebSocket 发送，等待回调显示
  chatService.send({
    type: 'chat',
    toUserId: currentContact.value.id,
    content: text,
    msgType: 'text'
  })
}

const sendEmoji = (emoji) => {
  inputText.value += emoji
  emojiVisible.value = false
}

// ======== 图片 & 文件上传 ========

const triggerImageUpload = () => { if (!uploading.value) imageInputRef.value?.click() }
const triggerFileUpload = () => { if (!uploading.value) fileInputRef.value?.click() }

const pushMessage = (msg) => {
  // 确定消息应该放在哪个联系人下
  let contactId
  if (msg.isSelf) {
    // 自己发送的消息，放到接收方（toUserId）下
    contactId = msg.toUserId
  } else {
    // 收到的消息，放到发送方（fromUserId）下
    contactId = msg.fromUserId
  }
  
  if (!messages.value[contactId]) messages.value[contactId] = []
  // 使用唯一 key 避免重复
  const msgKey = msg.tempType ? `temp_${msg.id}` : msg.id
  const exists = messages.value[contactId].some(m => (m.tempType ? `temp_${m.id}` : m.id) === msgKey)
  if (!exists) {
    messages.value[contactId].push(msg)
  }
}

const handleImageSelect = async (e) => {
  const file = e.target.files?.[0]
  if (!file || !currentContact.value) return
  e.target.value = ''
  uploading.value = true

  // 先在聊天窗口显示"发送中"
  const tempMsg = {
    id: ++tempMsgId,
    fromUserId: userStore.userId,
    toUserId: currentContact.value.id,
    content: '',
    type: 'image',
    tempType: 'image',
    isSelf: true,
    sending: true,
    failed: false,
    createTime: new Date(),
    fromColor: colors[Math.abs(Number(userStore.userId) % colors.length)]
  }
  pushMessage(tempMsg)
  scrollToBottom()

  try {
    const formData = new FormData()
    formData.append('file', file)
    const res = await uploadChatFile(formData)
    chatService.send({
      type: 'chat',
      toUserId: currentContact.value.id,
      content: res.url,
      msgType: 'image'
    })
    updateRecentChat(currentContact.value, '[图片]')
  } catch (err) {
    // 标记失败
    const list = messages.value[currentContact.value.id]
    if (list) {
      const idx = list.findIndex(m => m.id === tempMsg.id)
      if (idx > -1) {
        list[idx].sending = false
        list[idx].failed = true
      }
    }
    ElMessage.error('图片上传失败')
  } finally {
    uploading.value = false
  }
}

const handleFileSelect = async (e) => {
  const file = e.target.files?.[0]
  if (!file || !currentContact.value) return
  e.target.value = ''
  uploading.value = true

  const tempMsg = {
    id: ++tempMsgId,
    fromUserId: userStore.userId,
    toUserId: currentContact.value.id,
    content: '',
    type: 'file',
    tempType: 'file',
    fileName: file.name,
    fileSize: file.size,
    isSelf: true,
    sending: true,
    failed: false,
    createTime: new Date(),
    fromColor: colors[Math.abs(Number(userStore.userId) % colors.length)]
  }
  pushMessage(tempMsg)
  scrollToBottom()

  try {
    const formData = new FormData()
    formData.append('file', file)
    const res = await uploadChatFile(formData)
    chatService.send({
      type: 'chat',
      toUserId: currentContact.value.id,
      content: res.url,
      msgType: 'file',
      fileName: file.name,
      fileSize: file.size
    })
    updateRecentChat(currentContact.value, '[文件]')
  } catch (err) {
    const list = messages.value[currentContact.value.id]
    if (list) {
      const idx = list.findIndex(m => m.id === tempMsg.id)
      if (idx > -1) {
        list[idx].sending = false
        list[idx].failed = true
      }
    }
    ElMessage.error('文件上传失败')
  } finally {
    uploading.value = false
  }
}

// ======== 文件下载 ========

const downloadFile = async (msg) => {
  try {
    const res = await fetch(msg.content)
    if (!res.ok) throw new Error('下载失败')
    const blob = await res.blob()
    const a = document.createElement('a')
    a.href = URL.createObjectURL(blob)
    a.download = msg.fileName || '文件'
    document.body.appendChild(a)
    a.click()
    document.body.removeChild(a)
    URL.revokeObjectURL(a.href)
  } catch (e) {
    ElMessage.error('下载失败')
  }
}

// ======== 其他操作 ========

const deleteRecentChat = async (chat) => {
  try {
    await deleteConversation(chat.id)
  } catch (e) {
    // 即使 API 失败也删本地
  }
  const idx = recentChats.value.findIndex(c => c.id === chat.id)
  if (idx > -1) recentChats.value.splice(idx, 1)
  if (currentContact.value?.id === chat.id) currentContact.value = null
}

const scrollToBottom = () => {
  nextTick(() => {
    if (messagesRef.value) {
      messagesRef.value.scrollTop = messagesRef.value.scrollHeight
    }
  })
}

// ======== WebSocket ========

const onDialogOpen = () => {
  loadContacts().then(() => loadUnreadChats())
  currentUserName.value = userStore.username || ''
  currentUserNickname.value = userStore.nickname || ''
  chatService.connect()
  unsubscribeWs = chatService.onMessage((data) => {
    if (data.type === 'chat') {
      const msg = data.message
      const currentUserId = Number(userStore.userId)
      const msgFromUserId = Number(msg.fromUserId)
      const msgToUserId = Number(msg.toUserId)
      
      // 判断是否是自己发送的消息
      const isSelf = (msgFromUserId === currentUserId)
      msg.isSelf = isSelf
      msg.sending = false
      msg.failed = false
      msg.fromColor = colors[Math.abs(msgFromUserId % colors.length)]
      
      // 确定消息放在哪个联系人下
      // 自己发的消息放到接收方（toUserId）下，收到的消息放到发送方（fromUserId）下
      const contactId = isSelf ? msgToUserId : msgFromUserId
      
      if (!messages.value[contactId]) messages.value[contactId] = []
      const list = messages.value[contactId]
      
      // 清除同类型的临时消息（sending 状态的）
      if (isSelf && (msg.type === 'image' || msg.type === 'file')) {
        for (let i = list.length - 1; i >= 0; i--) {
          if (list[i].sending && list[i].tempType === msg.type) {
            list.splice(i, 1)
            break
          }
        }
      }
      
      // 去重（根据消息 id 或临时消息的 tempType）
      const msgKey = msg.tempType ? `temp_${msg.id}` : msg.id
      const exists = list.some(m => (m.tempType ? `temp_${m.id}` : m.id) === msgKey)
      if (!exists) list.push(msg)
      
      // 滚动
      if (currentContact.value && Number(currentContact.value.id) === contactId) {
        nextTick(() => scrollToBottom())
      }
      
      // 更新最近聊天
      if (!isSelf) {
        const contact = contacts.value.find(c => Number(c.id) === Number(msg.fromUserId))
        if (contact) {
          const lastMsg = msg.type === 'file' ? '[文件]' : (msg.type === 'image' ? '[图片]' : msg.content)
          updateRecentChat(contact, lastMsg)
          // 如果当前不在这个聊天窗口，增加未读计数
          const currentChatId = currentContact.value ? Number(currentContact.value.id) : null
          if (currentChatId !== Number(msg.fromUserId)) {
            const chat = recentChats.value.find(c => Number(c.id) === Number(msg.fromUserId))
            if (chat) chat.unread = (chat.unread || 0) + 1
          }
        }
      }
    } else if (data.type === 'online') {
      onlineUsers.value = new Set(data.users)
      contacts.value.forEach(c => { c.online = onlineUsers.value.has(Number(c.id)) })
      recentChats.value.forEach(c => { c.online = onlineUsers.value.has(Number(c.id)) })
    }
  })
}

const onDialogClose = () => {
  currentContact.value = null
  inputText.value = ''
  if (unsubscribeWs) { unsubscribeWs(); unsubscribeWs = null }
  // 通知导航栏刷新未读数
  emit('unread-change')
}

onMounted(() => { loadContacts() })
onUnmounted(() => { if (unsubscribeWs) unsubscribeWs() })
</script>

<style scoped>
.chat-container { display: flex; height: 600px; gap: 12px; }
.chat-sidebar { width: 280px; display: flex; flex-direction: column; border-right: 1px solid #e4e7ed; }
.sidebar-tabs { display: flex; border-bottom: 1px solid #e4e7ed; }
.sidebar-tab { flex: 1; padding: 12px; text-align: center; cursor: pointer; border-bottom: 2px solid transparent; transition: all 0.3s; display: flex; align-items: center; justify-content: center; gap: 6px; font-size: 13px; }
.sidebar-tab.active { border-bottom-color: #409eff; color: #409eff; }
.sidebar-search { padding: 8px; }
.sidebar-list { flex: 1; overflow-y: auto; }
.sidebar-item { display: flex; align-items: center; gap: 8px; padding: 8px 12px; cursor: pointer; transition: all 0.2s; border-left: 3px solid transparent; }
.sidebar-item:hover { background: #f5f7fa; }
.sidebar-item.active { background: #f0f9ff; border-left-color: #409eff; }
.sidebar-item-info { flex: 1; min-width: 0; }
.sidebar-item-name { font-size: 13px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sidebar-item-msg { font-size: 12px; color: #909399; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sidebar-item-status { font-size: 12px; color: #909399; }
.sidebar-item-status.online { color: #67c23a; }
.sidebar-empty { padding: 20px; text-align: center; color: #909399; font-size: 12px; }

.chat-main { flex: 1; display: flex; flex-direction: column; }
.chat-header { display: flex; align-items: center; gap: 8px; padding: 12px; border-bottom: 1px solid #e4e7ed; }
.chat-title { flex: 1; font-weight: 500; font-size: 14px; }
.chat-status { font-size: 12px; color: #909399; }
.chat-status.online { color: #67c23a; }

.chat-messages { flex: 1; overflow-y: auto; padding: 12px; display: flex; flex-direction: column; gap: 12px; }
.messages-loading { display: flex; align-items: center; justify-content: center; height: 100%; color: #909399; }

.message-wrapper { display: flex; gap: 8px; align-items: flex-end; }
.message-wrapper.self { flex-direction: row-reverse; }
.msg-avatar { flex-shrink: 0; cursor: pointer; }
.message-content { flex: 1; display: flex; flex-direction: column; gap: 4px; }
.message-wrapper.self .message-content { align-items: flex-end; }
.message-info { display: flex; gap: 8px; font-size: 12px; color: #909399; }
.sender-name { font-weight: 500; }

.message-bubble { background: #f0f0f0; padding: 8px 12px; border-radius: 8px; word-break: break-word; max-width: 300px; font-size: 13px; }
.message-wrapper.self .message-bubble { background: #409eff; color: white; }

/* 发送中 */
.message-sending { opacity: 0.6; }
.uploading-placeholder { display: flex; align-items: center; gap: 8px; font-size: 12px; color: #909399; }

/* 发送失败 */
.message-failed { background: #fef0f0 !important; color: #f56c6c !important; cursor: pointer; }
.message-failed:hover { background: #fde2e2 !important; }
.failed-hint { font-size: 11px; opacity: 0.8; }

/* 图片消息 */
.message-image { max-width: 200px; }
.message-image :deep(.el-image) { border-radius: 8px; overflow: hidden; }
.message-image :deep(.el-image img) { display: block; }

/* 文件消息 */
.message-file { display: flex; align-items: center; gap: 10px; background: #f8f9fa; padding: 10px 12px; border-radius: 8px; max-width: 260px; border: 1px solid #ebeef5; }
.message-wrapper.self .message-file { background: #ecf5ff; border-color: #b3d8ff; }
.file-icon { width: 36px; height: 36px; border-radius: 6px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.file-icon span { font-size: 10px; font-weight: 700; color: white; letter-spacing: 0.5px; }
.file-info { flex: 1; min-width: 0; }
.file-name { font-size: 13px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #303133; }
.file-size { font-size: 11px; color: #909399; margin-top: 2px; }

.message-emoji { font-size: 32px; }

.chat-toolbar { display: flex; gap: 8px; padding: 8px; border-top: 1px solid #e4e7ed; }
.chat-input-area { display: flex; flex-direction: column; gap: 8px; padding: 8px; border-top: 1px solid #e4e7ed; }
.chat-input-area :deep(.el-textarea) { flex: 1; }
.send-btn { align-self: flex-end; }

.emoji-panel { display: grid; grid-template-columns: repeat(10, 1fr); gap: 6px; padding: 4px; }
.emoji-item { font-size: 20px; cursor: pointer; text-align: center; padding: 4px; border-radius: 4px; transition: all 0.2s; }
.emoji-item:hover { background: #f0f0f0; transform: scale(1.2); }

.uploading-placeholder { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; padding: 20px; color: #909399; }
.uploading-placeholder .el-icon { font-size: 32px; color: #409eff; }
.uploading-placeholder span { font-size: 12px; }

.uploading-mini { display: inline-flex; align-items: center; gap: 6px; padding: 6px 10px; color: #409eff; font-size: 12px; background: #ecf5ff; border-radius: 4px; }
.uploading-mini .el-icon { font-size: 14px; }

.user-info-popover { display: flex; flex-direction: column; gap: 8px; padding: 8px 0; }
.user-info-row { display: flex; gap: 8px; font-size: 12px; line-height: 1.5; }
.user-info-label { font-weight: 500; min-width: 50px; color: #303133; }
.user-info-value { color: #606266; word-break: break-all; flex: 1; }
.signature-row { flex-direction: column; align-items: flex-start; }
.user-info-signature { color: #606266; font-size: 12px; line-height: 1.4; word-break: break-all; margin-top: 4px; }

.chat-empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; color: #909399; gap: 12px; }
.chat-empty p { margin: 0; font-size: 14px; }

.chat-dialog :deep(.el-dialog) { height: 720px; margin: auto !important; top: 50% !important; transform: translateY(-50%) !important; }
</style>
