Chat.vue 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. <template>
  2. <div class="chat-page">
  3. <div class="chat-header">
  4. <el-button text @click="router.back()">
  5. <el-icon><ArrowLeft /></el-icon>
  6. </el-button>
  7. <span class="chat-partner">{{ partnerName }}</span>
  8. </div>
  9. <div class="message-list" ref="messageListRef">
  10. <div
  11. v-for="msg in messages"
  12. :key="msg.id"
  13. :class="['message-row', msg.fromUserId === currentUserId ? 'own' : 'other']"
  14. >
  15. <el-avatar :src="msg.fromUserAvatar" :size="32" />
  16. <div class="message-bubble">
  17. <p>{{ msg.content }}</p>
  18. <el-image v-if="msg.imageUrl" :src="msg.imageUrl" fit="cover" class="msg-image" />
  19. </div>
  20. </div>
  21. <div v-if="loading" class="loading-more">
  22. <el-icon class="is-loading"><Loading /></el-icon>
  23. </div>
  24. </div>
  25. <div class="input-area">
  26. <el-input
  27. v-model="text"
  28. type="textarea"
  29. :rows="2"
  30. placeholder="输入消息..."
  31. @keyup.enter.ctrl="handleSend"
  32. />
  33. <el-button type="primary" @click="handleSend">
  34. 发送
  35. </el-button>
  36. </div>
  37. </div>
  38. </template>
  39. <script setup lang="ts">
  40. import { ref, onMounted, nextTick } from 'vue'
  41. import { useRoute, useRouter } from 'vue-router'
  42. import { ElMessage } from 'element-plus'
  43. import { ArrowLeft, Loading } from '@element-plus/icons-vue'
  44. import { getMessages } from '../api/endpoints/messages'
  45. import { useUserStore } from '../store'
  46. import type { MessageVO } from '../api/models'
  47. const route = useRoute()
  48. const router = useRouter()
  49. const userStore = useUserStore()
  50. const messageListRef = ref<HTMLElement | null>(null)
  51. const messages = ref<MessageVO[]>([])
  52. const text = ref('')
  53. const loading = ref(false)
  54. const partnerName = ref('')
  55. const currentUserId = userStore.state.userId
  56. const targetUserId = Number(route.params.userId)
  57. const scrollToBottom = async () => {
  58. await nextTick()
  59. if (messageListRef.value) {
  60. messageListRef.value.scrollTop = messageListRef.value.scrollHeight
  61. }
  62. }
  63. const loadMessages = async () => {
  64. loading.value = true
  65. try {
  66. const res = await getMessages().history(targetUserId)
  67. if (res.code === 200) {
  68. messages.value = (res.data?.list ?? []).reverse()
  69. if (messages.value.length > 0) {
  70. partnerName.value = messages.value[0].fromUserId === currentUserId
  71. ? (messages.value[0].toUserNickname ?? '')
  72. : (messages.value[0].fromUserNickname ?? '')
  73. }
  74. await scrollToBottom()
  75. }
  76. } finally {
  77. loading.value = false
  78. }
  79. }
  80. const handleSend = async () => {
  81. if (!text.value.trim()) return
  82. try {
  83. const res = await getMessages().send({ toUserId: targetUserId, content: text.value.trim() })
  84. if (res.code === 200) {
  85. messages.value.push({
  86. id: res.data,
  87. fromUserId: currentUserId,
  88. content: text.value.trim(),
  89. createdAt: new Date().toISOString(),
  90. } as MessageVO)
  91. text.value = ''
  92. await scrollToBottom()
  93. }
  94. } catch (err: unknown) {
  95. if (err && typeof err === 'object' && 'message' in err) {
  96. ElMessage.error((err as { message: string }).message)
  97. }
  98. }
  99. }
  100. onMounted(loadMessages)
  101. </script>
  102. <style scoped>
  103. .chat-page {
  104. display: flex;
  105. flex-direction: column;
  106. height: calc(100vh - 120px);
  107. max-width: 700px;
  108. margin: 0 auto;
  109. }
  110. .chat-header {
  111. display: flex;
  112. align-items: center;
  113. gap: 12px;
  114. padding: 12px 16px;
  115. border-bottom: 1px solid #e4e7ed;
  116. background: #fff;
  117. }
  118. .chat-partner {
  119. font-size: 16px;
  120. font-weight: 500;
  121. }
  122. .message-list {
  123. flex: 1;
  124. overflow-y: auto;
  125. padding: 16px;
  126. }
  127. .message-row {
  128. display: flex;
  129. gap: 8px;
  130. margin-bottom: 16px;
  131. }
  132. .message-row.own {
  133. flex-direction: row-reverse;
  134. }
  135. .message-bubble {
  136. max-width: 70%;
  137. padding: 10px 14px;
  138. border-radius: 12px;
  139. font-size: 14px;
  140. line-height: 1.5;
  141. }
  142. .message-row.other .message-bubble {
  143. background: #f0f0f0;
  144. border-bottom-left-radius: 4px;
  145. }
  146. .message-row.own .message-bubble {
  147. background: #409eff;
  148. color: #fff;
  149. border-bottom-right-radius: 4px;
  150. }
  151. .msg-image {
  152. width: 120px;
  153. height: 120px;
  154. border-radius: 4px;
  155. margin-top: 8px;
  156. cursor: pointer;
  157. }
  158. .loading-more {
  159. text-align: center;
  160. padding: 8px;
  161. }
  162. .input-area {
  163. display: flex;
  164. gap: 8px;
  165. padding: 12px 16px;
  166. border-top: 1px solid #e4e7ed;
  167. background: #fff;
  168. }
  169. .input-area .el-button {
  170. align-self: flex-end;
  171. }
  172. </style>