vue-chat-kit 0.3.9 → 0.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/vue-chat-kit.css +1 -1
  2. package/dist/vue-chat-kit.es.js +4420 -2877
  3. package/dist/vue-chat-kit.umd.js +1 -1
  4. package/package.json +1 -1
  5. package/src/components/AvatarCrop.vue +16 -127
  6. package/src/components/ChatPanel.vue +503 -2818
  7. package/src/components/EmojiPicker.vue +2 -73
  8. package/src/components/chat/ChatWindow.vue +177 -0
  9. package/src/components/chat/ContentList.vue +300 -0
  10. package/src/components/chat/ContextMenu.vue +32 -0
  11. package/src/components/chat/GroupSidebar.vue +284 -0
  12. package/src/components/chat/MainArea.vue +87 -0
  13. package/src/components/chat/Sidebar.vue +52 -0
  14. package/src/components/chat/dialogs/AddFriendDialog.vue +62 -0
  15. package/src/components/chat/dialogs/CreateGroupDialog.vue +86 -0
  16. package/src/components/chat/dialogs/GroupDetailDialog.vue +132 -0
  17. package/src/components/ui/Button.vue +190 -0
  18. package/src/components/ui/Dialog.vue +194 -0
  19. package/src/components/ui/Empty.vue +66 -0
  20. package/src/components/ui/Input.vue +166 -0
  21. package/src/components/ui/Message.vue +186 -0
  22. package/src/components/ui/MessageBox.vue +143 -0
  23. package/src/components/ui/MessageManager.vue +92 -0
  24. package/src/components/ui/Switch.vue +65 -0
  25. package/src/components/ui/Tag.vue +68 -0
  26. package/src/components/ui/icons/ArrowDown.vue +5 -0
  27. package/src/components/ui/icons/ArrowRight.vue +5 -0
  28. package/src/components/ui/icons/Bell.vue +6 -0
  29. package/src/components/ui/icons/Camera.vue +6 -0
  30. package/src/components/ui/icons/ChatDotRound.vue +5 -0
  31. package/src/components/ui/icons/Check.vue +5 -0
  32. package/src/components/ui/icons/CircleCheck.vue +6 -0
  33. package/src/components/ui/icons/Clock.vue +6 -0
  34. package/src/components/ui/icons/Close.vue +5 -0
  35. package/src/components/ui/icons/Delete.vue +8 -0
  36. package/src/components/ui/icons/Edit.vue +6 -0
  37. package/src/components/ui/icons/Folder.vue +5 -0
  38. package/src/components/ui/icons/Minus.vue +5 -0
  39. package/src/components/ui/icons/Monitor.vue +7 -0
  40. package/src/components/ui/icons/Moon.vue +5 -0
  41. package/src/components/ui/icons/Picture.vue +7 -0
  42. package/src/components/ui/icons/Plus.vue +5 -0
  43. package/src/components/ui/icons/Search.vue +6 -0
  44. package/src/components/ui/icons/Setting.vue +6 -0
  45. package/src/components/ui/icons/Sunny.vue +6 -0
  46. package/src/components/ui/icons/User.vue +6 -0
  47. package/src/components/ui/icons/UserFilled.vue +6 -0
  48. package/src/components/ui/icons/Warning.vue +5 -0
  49. package/src/components/ui/icons/index.js +24 -0
  50. package/src/components/ui/index.js +10 -0
  51. package/src/composables/useFriendChat.js +10 -14
  52. package/src/composables/useGroupChat.js +140 -48
  53. package/src/composables/useMessage.js +21 -0
  54. package/src/composables/useMessageBox.js +98 -0
  55. package/src/composables/useTheme.js +140 -0
  56. package/src/config/index.js +1 -0
  57. package/src/const/index.js +1 -0
  58. package/src/const/theme.js +19 -0
  59. package/src/core/api.js +13 -2
  60. package/src/index.js +5 -5
  61. package/src/styles/_base.scss +38 -0
  62. package/src/styles/_variables.scss +43 -0
  63. package/src/styles/components/_add-friend-dialog.scss +45 -0
  64. package/src/styles/components/_avatar-crop.scss +120 -0
  65. package/src/styles/components/_chat-panel.scss +546 -0
  66. package/src/styles/components/_chat-window.scss +239 -0
  67. package/src/styles/components/_content-list.scss +260 -0
  68. package/src/styles/components/_context-menu.scss +35 -0
  69. package/src/styles/components/_create-group-dialog.scss +78 -0
  70. package/src/styles/components/_dialogs.scss +226 -0
  71. package/src/styles/components/_emoji-picker.scss +74 -0
  72. package/src/styles/components/_group-detail-dialog.scss +110 -0
  73. package/src/styles/components/_group-sidebar.scss +278 -0
  74. package/src/styles/components/_main-area.scss +94 -0
  75. package/src/styles/components/_sidebar.scss +83 -0
  76. package/src/styles/index.scss +18 -0
  77. package/src/styles/themes/_dark.scss +68 -0
  78. package/src/styles/themes/_index.scss +7 -0
  79. package/src/styles/themes/_light.scss +69 -0
@@ -1,713 +1,146 @@
1
1
  <template>
2
- <div class="chat-panel">
2
+ <div class="chat-panel" :data-theme="appliedTheme">
3
3
  <!-- 左侧图标导航栏 -->
4
- <div class="chat-sidebar">
5
- <div class="sidebar-avatar" @click="handleAvatarClick">
6
- <img :src="myAvatar" alt="头像" class="sidebar-avatar-img" />
7
- </div>
8
-
9
- <div
10
- v-for="tab in navTabs"
11
- :key="tab.id"
12
- :class="[
13
- 'sidebar-nav-item',
14
- currentNavTab === tab.id ? 'sidebar-nav-item-active' : 'sidebar-nav-item-inactive'
15
- ]"
16
- @click="currentNavTab = tab.id"
17
- >
18
- <el-icon :size="24">
19
- <component :is="tab.icon" />
20
- </el-icon>
21
- <span v-if="tab.badge" class="sidebar-nav-badge">
22
- {{ tab.badge > 99 ? '99+' : tab.badge }}
23
- </span>
24
- </div>
25
-
26
- <div class="sidebar-spacer"></div>
27
-
28
- <div
29
- v-if="config.modules.settings"
30
- class="sidebar-nav-item sidebar-nav-item-inactive"
31
- @click="showSettingsDialog = true"
32
- title="设置"
33
- >
34
- <el-icon :size="24"><Setting /></el-icon>
35
- </div>
36
- </div>
4
+ <Sidebar
5
+ :my-avatar="myAvatar"
6
+ :nav-tabs="navTabs"
7
+ v-model:current-nav-tab="currentNavTab"
8
+ :config="config"
9
+ @avatar-click="handleAvatarClick"
10
+ @settings-click="showSettingsDialog = true"
11
+ />
37
12
 
38
13
  <!-- 中间内容栏 -->
39
- <div class="chat-content-panel">
40
- <!-- 搜索栏 -->
41
- <div class="chat-search-bar">
42
- <el-input
43
- v-model="searchText"
44
- placeholder="搜索"
45
- :prefix-icon="Search"
46
- class="chat-search-input"
47
- />
48
- </div>
49
-
50
- <!-- 内容区域 -->
51
- <div class="chat-content-scroll">
52
- <!-- 聊天列表 -->
53
- <div v-if="currentNavTab === 'chat'">
54
- <div
55
- v-for="chat in filteredUsers"
56
- :key="chat.id"
57
- :class="[
58
- 'chat-list-item',
59
- currentChatId === chat.id ? 'chat-list-item-active' : ''
60
- ]"
61
- @click="selectChat(chat)"
62
- @contextmenu.prevent.stop="showContextMenuFn($event, chat)"
63
- >
64
- <div class="chat-list-avatar-wrapper">
65
- <img
66
- :src="chat.avatar"
67
- :alt="chat.name"
68
- class="chat-list-avatar"
69
- />
70
- <span
71
- v-if="chat.online"
72
- class="chat-list-online-indicator"
73
- ></span>
74
- </div>
75
- <div class="chat-list-info">
76
- <div class="chat-list-header">
77
- <span class="chat-list-name">{{ chat.name }}</span>
78
- <span class="chat-list-time">{{ formatLastTime(chat.lastTime) }}</span>
79
- </div>
80
- <div class="chat-list-preview">
81
- <span class="chat-list-last-msg">{{ chat.lastMsg }}</span>
82
- <span
83
- v-if="chat.unread > 0"
84
- class="chat-list-unread"
85
- >
86
- {{ chat.unread > 99 ? '99+' : chat.unread }}
87
- </span>
88
- </div>
89
- </div>
90
- </div>
91
- </div>
92
-
93
- <!-- 好友列表 -->
94
- <div v-if="currentNavTab === 'friends' && config.modules.friends">
95
- <div class="add-friend-section">
96
- <div
97
- class="add-friend-btn"
98
- @click="openAddFriendDialog"
99
- >
100
- <div class="add-friend-icon">
101
- <el-icon class="text-white" :size="20"><Plus /></el-icon>
102
- </div>
103
- <span class="add-friend-text">添加好友</span>
104
- </div>
105
- </div>
106
- <div
107
- v-for="friend in filteredFriendList"
108
- :key="friend.id"
109
- class="chat-list-item"
110
- @click="selectFriend(friend)"
111
- >
112
- <div class="chat-list-avatar-wrapper">
113
- <img
114
- :src="friend.avatar"
115
- :alt="friend.name"
116
- class="chat-list-avatar"
117
- />
118
- <span
119
- :class="[
120
- 'chat-list-online-indicator',
121
- friend.online ? 'chat-list-online' : 'chat-list-offline'
122
- ]"
123
- ></span>
124
- </div>
125
- <div class="chat-list-info">
126
- <span class="chat-list-name">{{ friend.name }}</span>
127
- </div>
128
- </div>
129
- </div>
130
-
131
- <!-- 申请列表 -->
132
- <div v-if="currentNavTab === 'apply' && config.modules.apply">
133
- <el-empty v-if="loadingFriendApply" description="加载中..." />
134
- <el-empty
135
- v-else-if="friendApplyList.length === 0"
136
- description="暂无好友申请"
137
- />
138
- <div
139
- v-else
140
- v-for="apply in friendApplyList"
141
- :key="apply.applyUser || apply.id"
142
- class="friend-request-item"
143
- >
144
- <div class="friend-request-info">
145
- <img
146
- :src="`https://api.dicebear.com/7.x/avataaars/svg?seed=${apply.applyUser}`"
147
- :alt="apply.applyUser"
148
- class="friend-request-avatar"
149
- />
150
- <div class="friend-request-details">
151
- <div class="friend-request-username">{{ apply.applyUser }}</div>
152
- <div class="friend-request-desc">请求添加你为好友</div>
153
- </div>
154
- </div>
155
- <el-button
156
- type="primary"
157
- size="small"
158
- @click="agreeFriend(apply.applyUser)"
159
- >同意</el-button>
160
- </div>
161
- </div>
162
-
163
- <!-- 群聊列表 -->
164
- <div v-if="currentNavTab === 'groups' && config.modules.groups">
165
- <div class="add-friend-section">
166
- <div
167
- class="add-friend-btn"
168
- @click="createGroupDialogVisible = true"
169
- >
170
- <div class="add-friend-icon">
171
- <el-icon class="text-white" :size="20"><Plus /></el-icon>
172
- </div>
173
- <span class="add-friend-text">创建群聊</span>
174
- </div>
175
- </div>
176
- <div
177
- v-for="group in filteredGroupList"
178
- :key="group.groupId"
179
- :class="[
180
- 'chat-list-item',
181
- currentSelectGroup?.groupId === group.groupId ? 'chat-list-item-active' : ''
182
- ]"
183
- @click="selectGroupChat(group)"
184
- >
185
- <div class="chat-list-avatar-wrapper">
186
- <!-- 多方格群聊头像 -->
187
- <div v-if="group.memberAvatars && group.memberAvatars.length > 0" class="group-avatar-grid">
188
- <div
189
- v-for="(member, index) in group.memberAvatars"
190
- :key="member.username"
191
- class="group-avatar-item"
192
- :style="getGroupAvatarGridStyle(group.memberAvatars.length, index)"
193
- >
194
- <img
195
- :src="member.avatar"
196
- :alt="member.username"
197
- class="group-avatar-img"
198
- />
199
- </div>
200
- </div>
201
- <!-- 单个头像作为后备 -->
202
- <img
203
- v-else
204
- :src="group.avatar"
205
- :alt="group.name"
206
- class="chat-list-avatar"
207
- />
208
- </div>
209
- <div class="chat-list-info">
210
- <div class="chat-list-header">
211
- <span class="chat-list-name">{{ group.name }}</span>
212
- <span class="chat-list-time">{{ formatLastTime(group.lastTime) }}</span>
213
- </div>
214
- <div class="chat-list-preview">
215
- <span class="chat-list-last-msg">{{ group.lastMsg }}</span>
216
- <span
217
- v-if="group.unread > 0"
218
- class="chat-list-unread"
219
- >
220
- {{ group.unread > 99 ? '99+' : group.unread }}
221
- </span>
222
- </div>
223
- </div>
224
- </div>
225
- </div>
226
- </div>
227
- </div>
14
+ <ContentList
15
+ v-model:search-text="searchText"
16
+ :current-nav-tab="currentNavTab"
17
+ :config="config"
18
+ :combined-chat-list="combinedChatList"
19
+ :filtered-friend-list="filteredFriendList"
20
+ :filtered-group-list="filteredGroupList"
21
+ :current-chat-id="currentChatId"
22
+ :current-select-group="currentSelectGroup"
23
+ :current-selected-group="currentSelectedGroup"
24
+ :loading-friend-apply="loadingFriendApply"
25
+ :friend-apply-list="friendApplyList"
26
+ :format-last-time="formatLastTime"
27
+ @add-friend="openAddFriendDialog"
28
+ @create-group="createGroupDialogVisible = true"
29
+ @select-chat="selectChat"
30
+ @select-group-chat="selectGroupChat"
31
+ @select-friend="selectFriend"
32
+ @select-group="selectGroup"
33
+ @agree-friend="agreeFriend"
34
+ @contextmenu="showContextMenuFn"
35
+ />
228
36
 
229
37
  <!-- 右侧聊天/详情区域 -->
230
- <div class="chat-main-area">
231
- <!-- 好友信息展示区域 -->
232
- <div v-if="currentSelectedFriend && !currentChat" class="friend-profile-area">
233
- <img
234
- :src="currentSelectedFriend.avatar"
235
- :alt="currentSelectedFriend.name"
236
- class="profile-avatar"
237
- />
238
- <div class="profile-name">{{ currentSelectedFriend.name }}</div>
239
- <div class="profile-status">
240
- <span
241
- :class="[
242
- 'profile-status-dot',
243
- currentSelectedFriend.online ? 'profile-status-online' : 'profile-status-offline'
244
- ]"
245
- ></span>
246
- <span>{{ currentSelectedFriend.online ? '在线' : '离线' }}</span>
247
- </div>
248
- <el-button
249
- type="primary"
250
- size="large"
251
- @click="handleStartChat"
252
- class="profile-start-chat-btn"
253
- >
254
- <el-icon><ChatDotRound /></el-icon>
255
- <span>发消息</span>
256
- </el-button>
257
- </div>
258
-
259
- <!-- 聊天窗口(群聊或单聊) -->
260
- <div v-if="currentChat || currentSelectGroup" class="chat-window-area">
261
- <!-- 顶部标题栏 -->
262
- <div class="chat-window-header">
263
- <div class="chat-window-title">
264
- <span class="chat-window-name">{{ currentSelectGroup ? currentSelectGroup.name : currentChat.name }}</span>
265
- <span
266
- v-if="!currentSelectGroup"
267
- :class="[
268
- 'chat-window-status',
269
- currentChat.online ? 'chat-window-status-online' : 'chat-window-status-offline'
270
- ]"
271
- >
272
- {{ currentChat.online ? '在线' : '离线' }}
273
- </span>
274
- <span v-if="currentSelectGroup" class="chat-window-status">
275
- {{ groupMemberList.length }} 人
276
- </span>
277
- </div>
278
- <div class="chat-window-actions">
279
- <el-icon class="chat-action-icon"><Search /></el-icon>
280
- <el-icon
281
- v-if="currentSelectGroup"
282
- class="chat-action-icon"
283
- @click="showGroupSidebar = !showGroupSidebar"
284
- ><MoreFilled /></el-icon>
285
- <el-icon
286
- v-else
287
- class="chat-action-icon"
288
- @click="showChatDetail = !showChatDetail"
289
- ><MoreFilled /></el-icon>
290
- </div>
291
- </div>
292
-
293
- <!-- 聊天消息区域 -->
294
- <div
295
- ref="messagesContainer"
296
- class="chat-messages-container"
38
+ <MainArea
39
+ :current-selected-friend="currentSelectedFriend"
40
+ :current-selected-group="currentSelectedGroup"
41
+ :current-chat="currentChat"
42
+ :current-select-group="currentSelectGroup"
43
+ :current-nav-tab="currentNavTab"
44
+ @start-chat="handleStartChat"
45
+ @start-group-chat="handleStartGroupChat"
46
+ >
47
+ <template #chat-window>
48
+ <ChatWindow
49
+ ref="chatWindowRef"
50
+ :current-chat="currentChat"
51
+ :current-select-group="currentSelectGroup"
52
+ :group-member-list="groupMemberList"
53
+ :messages="currentSelectGroup ? currentGroupMessages : currentMessages"
54
+ :format-time="formatTime"
55
+ :format-file-size="formatFileSize"
56
+ @toggle-group-sidebar="showGroupSidebar = !showGroupSidebar"
57
+ @toggle-chat-detail="showChatDetail = !showChatDetail"
58
+ @open-file="openFile"
59
+ @hide-emoji="showEmojiPicker = false"
297
60
  >
298
- <div
299
- v-for="(msg, index) in currentSelectGroup ? currentGroupMessages : currentMessages"
300
- :key="index"
301
- :class="[
302
- 'message-wrapper',
303
- msg.isSelf ? 'message-self' : 'message-other'
304
- ]"
305
- >
306
- <!-- 头像 -->
307
- <div class="message-avatar">
308
- <img
309
- :src="msg.avatar"
310
- class="message-avatar-img"
311
- />
312
- </div>
313
-
314
- <!-- 消息内容 -->
315
- <div
316
- :class="[
317
- 'message-content',
318
- msg.isSelf ? 'message-content-self' : 'message-content-other'
319
- ]"
320
- >
321
- <div v-if="!msg.isSelf" class="message-sender-name">
322
- {{ currentSelectGroup ? (msg.displayName || msg.sendUsername) : currentChat.name }}
323
- </div>
324
-
325
- <div class="message-bubble-wrapper">
326
- <!-- 文本消息 -->
327
- <div
328
- v-if="msg.type === 'text'"
329
- :class="[
330
- 'message-bubble',
331
- msg.isSelf ? 'message-bubble-self' : 'message-bubble-other'
332
- ]"
333
- >
334
- {{ msg.text }}
335
- </div>
336
-
337
- <!-- 图片文件消息 -->
338
- <div
339
- v-else-if="msg.type === 'file' && msg.fileType === 'image'"
340
- :class="[
341
- 'message-bubble',
342
- 'message-image-bubble',
343
- msg.isSelf ? 'message-bubble-self' : 'message-bubble-other'
344
- ]"
345
- @click="openFile(msg.fileUrl)"
346
- >
347
- <img
348
- :src="msg.fileUrl"
349
- :alt="msg.fileName"
350
- class="message-image"
351
- @error="handleImageError"
352
- />
353
- <div v-if="msg.fileSize" class="message-image-size">{{ formatFileSize(msg.fileSize) }}</div>
61
+ <template #input-area>
62
+ <!-- 待发送文件预览 -->
63
+ <div v-if="pendingFiles.length > 0" class="pending-files-area">
64
+ <div v-for="(file, index) in pendingFiles" :key="file.id" class="pending-file-item">
65
+ <div v-if="file.isImage" class="pending-image-wrapper">
66
+ <img :src="file.previewUrl" :alt="file.name" class="pending-image" />
67
+ <button @click="removePendingFile(index)" class="pending-file-remove-btn">×</button>
354
68
  </div>
355
-
356
- <!-- 文档文件消息 -->
357
- <div
358
- v-else-if="msg.type === 'file'"
359
- :class="[
360
- 'message-bubble',
361
- 'message-file-bubble',
362
- msg.isSelf ? 'message-bubble-self' : 'message-bubble-other'
363
- ]"
364
- @click="openFile(msg.fileUrl)"
365
- >
366
- <div class="message-file-content">
367
- <div class="message-file-icon">
368
- <el-icon :size="28"><Document /></el-icon>
369
- </div>
370
- <div class="message-file-info">
371
- <div class="message-file-name">{{ msg.fileName || msg.text }}</div>
372
- <div class="message-file-meta">
373
- <el-icon :size="12"><Download /></el-icon>
374
- <span>点击下载</span>
375
- <span v-if="msg.fileSize">· {{ formatFileSize(msg.fileSize) }}</span>
376
- </div>
377
- </div>
378
- </div>
379
- </div>
380
-
381
- <!-- 时间显示在气泡下方 -->
382
- <div
383
- :class="[
384
- 'message-time',
385
- msg.isSelf ? 'message-time-right' : 'message-time-left'
386
- ]"
387
- >
388
- {{ formatTime(msg.time) }}
69
+ <div v-else class="pending-file-wrapper">
70
+ <span class="pending-file-icon"><Folder /></span>
71
+ <span class="pending-file-name">{{ file.name }}</span>
72
+ <button @click="removePendingFile(index)" class="pending-file-remove-btn">×</button>
389
73
  </div>
390
74
  </div>
391
75
  </div>
392
- </div>
393
- </div>
394
76
 
395
- <!-- 底部输入区域 -->
396
- <div class="chat-input-area" @click="showEmojiPicker = false">
397
- <!-- 待发送文件预览 -->
398
- <div
399
- v-if="pendingFiles.length > 0"
400
- class="pending-files-area"
401
- >
402
- <div
403
- v-for="(file, index) in pendingFiles"
404
- :key="file.id"
405
- class="pending-file-item"
406
- >
407
- <!-- 图片预览 -->
408
- <div
409
- v-if="file.isImage"
410
- class="pending-image-wrapper"
411
- >
412
- <img
413
- :src="file.previewUrl"
414
- :alt="file.name"
415
- class="pending-image"
416
- />
417
- <button
418
- @click="removePendingFile(index)"
419
- class="pending-file-remove-btn"
420
- >
421
- ×
422
- </button>
423
- </div>
424
- <!-- 非图片文件预览 -->
425
- <div
426
- v-else
427
- class="pending-file-wrapper"
428
- >
429
- <el-icon class="pending-file-icon"><Folder /></el-icon>
430
- <span class="pending-file-name">{{ file.name }}</span>
431
- <button
432
- @click="removePendingFile(index)"
433
- class="pending-file-remove-btn"
434
- >
435
- ×
436
- </button>
77
+ <div v-if="config.modules.fileUpload" class="input-toolbar">
78
+ <div class="emoji-button-wrapper">
79
+ <span class="input-toolbar-icon" @click.stop="showEmojiPicker = !showEmojiPicker"><ChatDotRound /></span>
80
+ <EmojiPicker :visible="showEmojiPicker" @select="selectEmoji" />
437
81
  </div>
82
+ <span class="input-toolbar-icon" @click="triggerFileSelect"><Folder /></span>
83
+ <span class="input-toolbar-icon"><Picture /></span>
438
84
  </div>
439
- </div>
440
-
441
- <div v-if="config.modules.fileUpload" class="input-toolbar">
442
- <div class="emoji-button-wrapper">
443
- <el-icon
444
- class="input-toolbar-icon"
445
- @click.stop="showEmojiPicker = !showEmojiPicker"
446
- ><ChatDotRound /></el-icon>
447
- <EmojiPicker
448
- :visible="showEmojiPicker"
449
- @select="selectEmoji"
85
+ <div class="input-textarea-wrapper">
86
+ <textarea
87
+ v-model="inputText"
88
+ @keydown.enter.prevent="handleSend"
89
+ @paste="handlePaste"
90
+ placeholder="输入消息或粘贴文件..."
91
+ class="message-input-textarea"
92
+ rows="3"
450
93
  />
451
94
  </div>
452
- <el-icon
453
- class="input-toolbar-icon"
454
- @click="triggerFileSelect"
455
- ><Folder /></el-icon>
456
- <el-icon class="input-toolbar-icon"><Picture /></el-icon>
457
- </div>
458
- <div class="input-textarea-wrapper">
459
- <textarea
460
- v-model="inputText"
461
- @keydown.enter.prevent="handleSend"
462
- @paste="handlePaste"
463
- placeholder="输入消息或粘贴文件..."
464
- class="message-input-textarea"
465
- rows="3"
466
- />
467
- </div>
468
- <div class="input-send-wrapper">
469
- <el-button
470
- type="primary"
471
- :disabled="!inputText.trim() && pendingFiles.length === 0"
472
- @click="handleSend"
473
- class="send-message-btn"
474
- >
475
- 发送
476
- </el-button>
477
- </div>
478
-
479
- <!-- 隐藏的文件 input -->
480
- <input
481
- ref="fileInputRef"
482
- type="file"
483
- multiple
484
- class="hidden-file-input"
485
- @change="handleFileSelect"
486
- />
487
- </div>
488
- </div>
489
-
490
- <!-- 空状态 -->
491
- <div
492
- v-else-if="!currentSelectedFriend"
493
- class="chat-empty-state"
494
- >
495
- <el-icon :size="64" class="empty-state-icon"><ChatLineRound /></el-icon>
496
- <div class="empty-state-text">
497
- {{ currentNavTab === 'apply' ? '在左侧选择好友申请' : '在左侧选择好友开始聊天' }}
498
- </div>
499
- </div>
500
- </div>
501
-
502
- <!-- 群聊侧边栏 -->
503
- <transition name="slide">
504
- <div
505
- v-if="showGroupSidebar && currentSelectGroup"
506
- class="group-sidebar"
507
- >
508
- <!-- 关闭按钮 -->
509
- <div class="group-sidebar-header">
510
- <span class="group-sidebar-title">群聊信息</span>
511
- <el-icon class="group-sidebar-close" @click="showGroupSidebar = false">
512
- <Close />
513
- </el-icon>
514
- </div>
515
-
516
- <div class="group-sidebar-content">
517
- <!-- 群成员头像展示区 -->
518
- <div class="group-members-avatar-section">
519
- <div class="group-members-grid">
520
- <div
521
- v-for="member in groupMemberList.slice(0, 12)"
522
- :key="member.username"
523
- class="group-member-avatar-item"
524
- @click="openEditMemberNick(member)"
525
- >
526
- <img
527
- :src="member.avatar ? `${config.api.baseUrl}${member.avatar}` : `https://api.dicebear.com/7.x/avataaars/svg?seed=${member.username}`"
528
- :alt="member.username"
529
- class="group-member-avatar-small"
530
- />
531
- <span class="group-member-nickname">{{ member.memberNick || member.username }}</span>
532
- </div>
533
- <div
534
- v-if="groupMemberList.length > 12"
535
- class="group-member-avatar-item"
536
- @click="inviteMemberDialogVisible = true"
537
- >
538
- <div class="group-member-add-icon">
539
- <el-icon><Plus /></el-icon>
540
- </div>
541
- <span class="group-member-nickname">邀请</span>
542
- </div>
543
- <div
544
- v-else
545
- class="group-member-avatar-item"
546
- @click="inviteMemberDialogVisible = true"
547
- >
548
- <div class="group-member-add-icon">
549
- <el-icon><Plus /></el-icon>
550
- </div>
551
- <span class="group-member-nickname">邀请</span>
552
- </div>
553
- </div>
554
- </div>
555
-
556
- <!-- 分割线 -->
557
- <div class="group-divider"></div>
558
-
559
- <!-- 群设置项 -->
560
- <div class="group-settings-section">
561
- <!-- 群名称 -->
562
- <div class="group-setting-item" v-if="currentSelectGroup?.owner === myUsername">
563
- <span class="group-setting-label">群聊名称</span>
564
- <div class="group-setting-value-wrapper">
565
- <div v-if="editingFields.groupNickname" class="group-edit-wrapper">
566
- <el-input
567
- v-model="tempEditValues.groupNickname"
568
- placeholder="请输入群聊名称"
569
- @keyup.enter="handleSaveEditField('groupNickname')"
570
- class="group-input-edit"
571
- />
572
- <div class="group-edit-buttons">
573
- <el-button size="small" @click="cancelEditField('groupNickname')">取消</el-button>
574
- <el-button size="small" type="primary" @click="handleSaveEditField('groupNickname')">保存</el-button>
575
- </div>
576
- </div>
577
- <div
578
- v-else
579
- class="group-setting-value"
580
- @click="startEditField('groupNickname')"
581
- >
582
- <span>{{ currentGroupInfo?.groupNickname || currentGroupInfo?.groupName || currentSelectGroup?.name || '' }}</span>
583
- <el-icon><Edit /></el-icon>
584
- </div>
585
- </div>
586
- </div>
587
- <div class="group-setting-item" v-else>
588
- <span class="group-setting-label">群聊名称</span>
589
- <span class="group-setting-value-text">{{ currentGroupInfo?.groupNickname || currentGroupInfo?.groupName || currentSelectGroup?.name || '' }}</span>
590
- </div>
591
-
592
- <!-- 群公告 -->
593
- <div class="group-setting-item" v-if="currentSelectGroup?.owner === myUsername">
594
- <span class="group-setting-label">群公告</span>
595
- <div class="group-setting-value-wrapper">
596
- <div v-if="editingFields.notice" class="group-edit-wrapper">
597
- <el-input
598
- v-model="tempEditValues.notice"
599
- type="textarea"
600
- :rows="3"
601
- placeholder="请输入群公告"
602
- class="group-input-edit"
603
- />
604
- <div class="group-edit-buttons">
605
- <el-button size="small" @click="cancelEditField('notice')">取消</el-button>
606
- <el-button size="small" type="primary" @click="handleSaveEditField('notice')">保存</el-button>
607
- </div>
608
- </div>
609
- <div
610
- v-else
611
- class="group-setting-value"
612
- @click="startEditField('notice')"
613
- >
614
- <span class="group-setting-value-text">{{ currentGroupInfo?.notice || '暂无公告' }}</span>
615
- <el-icon><Edit /></el-icon>
616
- </div>
617
- </div>
618
- </div>
619
- <div class="group-setting-item" v-else>
620
- <span class="group-setting-label">群公告</span>
621
- <span class="group-setting-value-text">{{ currentGroupInfo?.notice || '暂无公告' }}</span>
622
- </div>
623
-
624
- <!-- 备注 -->
625
- <div class="group-setting-item" v-if="currentSelectGroup?.owner === myUsername">
626
- <span class="group-setting-label">备注</span>
627
- <div class="group-setting-value-wrapper">
628
- <div v-if="editingFields.remark" class="group-edit-wrapper">
629
- <el-input
630
- v-model="tempEditValues.remark"
631
- placeholder="请输入备注"
632
- @keyup.enter="handleSaveEditField('remark')"
633
- class="group-input-edit"
634
- />
635
- <div class="group-edit-buttons">
636
- <el-button size="small" @click="cancelEditField('remark')">取消</el-button>
637
- <el-button size="small" type="primary" @click="handleSaveEditField('remark')">保存</el-button>
638
- </div>
639
- </div>
640
- <div
641
- v-else
642
- class="group-setting-value"
643
- @click="startEditField('remark')"
644
- >
645
- <span class="group-setting-value-text">{{ currentGroupInfo?.remark || '群聊的备注仅自己可见' }}</span>
646
- <el-icon><Edit /></el-icon>
647
- </div>
648
- </div>
649
- </div>
650
- <div class="group-setting-item" v-else>
651
- <span class="group-setting-label">备注</span>
652
- <span class="group-setting-value-text">{{ currentGroupInfo?.remark || '群聊的备注仅自己可见' }}</span>
653
- </div>
654
-
655
- <!-- 我在本群的昵称 -->
656
- <div class="group-setting-item" @click="openMyNicknameEdit">
657
- <span class="group-setting-label">我在本群的昵称</span>
658
- <div class="group-setting-value">
659
- <span class="group-setting-value-text">{{ myNicknameInGroup || myUsername }}</span>
660
- <el-icon><Edit /></el-icon>
661
- </div>
662
- </div>
663
-
664
- <div class="group-divider"></div>
665
-
666
- <!-- 查找聊天内容 -->
667
- <div class="group-setting-item">
668
- <span class="group-setting-label">查找聊天内容</span>
669
- <el-icon><ArrowRight /></el-icon>
670
- </div>
671
-
672
- <!-- 消息免打扰 -->
673
- <div class="group-setting-item">
674
- <span class="group-setting-label">消息免打扰</span>
675
- <el-switch v-model="muteGroup" />
95
+ <div class="input-send-wrapper">
96
+ <Button
97
+ type="primary"
98
+ :disabled="!inputText.trim() && pendingFiles.length === 0"
99
+ @click="handleSend"
100
+ class="send-message-btn"
101
+ >发送</Button>
676
102
  </div>
103
+ <input ref="fileInputRef" type="file" multiple class="hidden-file-input" @change="handleFileSelect" />
104
+ </template>
105
+ </ChatWindow>
106
+ </template>
107
+ </MainArea>
677
108
 
678
- <div class="group-divider"></div>
679
-
680
- <!-- 群主专属功能 -->
681
- <template v-if="currentSelectGroup.owner === myUsername">
682
- <div class="group-setting-item danger" @click="handleDeleteGroup">
683
- <span class="group-setting-label">解散群聊</span>
684
- </div>
685
- </template>
686
-
687
- <!-- 普通成员退出 -->
688
- <template v-else>
689
- <div class="group-setting-item danger" @click="handleQuitGroup">
690
- <span class="group-setting-label">删除并退出</span>
691
- </div>
692
- </template>
693
- </div>
694
- </div>
695
- </div>
696
- </transition>
109
+ <!-- 群聊侧边栏 -->
110
+ <GroupSidebar
111
+ :visible="showGroupSidebar && !!currentSelectGroup"
112
+ :current-select-group="currentSelectGroup"
113
+ :group-member-list="groupMemberList"
114
+ :is-remove-member-mode="isRemoveMemberMode"
115
+ :config="config"
116
+ :my-username="myUsername"
117
+ :editing-fields="editingFields"
118
+ :temp-edit-values="tempEditValues"
119
+ :current-group-info="currentGroupInfo"
120
+ :my-nickname-in-group="myNicknameInGroup"
121
+ :group-owner-username="groupOwnerUsername"
122
+ v-model:mute-group="muteGroup"
123
+ @close="showGroupSidebar = false"
124
+ @remove-member="handleRemoveMember"
125
+ @edit-member-nick="openEditMemberNick"
126
+ @invite-member="inviteMemberDialogVisible = true; isRemoveMemberMode = false"
127
+ @toggle-remove-mode="isRemoveMemberMode = !isRemoveMemberMode"
128
+ @start-edit="startEditField"
129
+ @cancel-edit="cancelEditField"
130
+ @save-field="handleSaveEditField"
131
+ @update:temp-edit-value="({ field, value }) => tempEditValues[field] = value"
132
+ @edit-my-nick="openMyNicknameEdit"
133
+ @show-group-detail="groupDetailVisible = true"
134
+ @delete-group="handleDeleteGroup"
135
+ @quit-group="handleQuitGroup"
136
+ />
697
137
 
698
138
  <!-- 右侧详情面板 -->
699
- <div
700
- v-if="showChatDetail"
701
- class="chat-detail-panel"
702
- >
139
+ <div v-if="showChatDetail" class="chat-detail-panel">
703
140
  <div class="chat-detail-header">聊天详情</div>
704
141
  <div class="chat-detail-content">
705
142
  <div class="chat-detail-profile">
706
- <img
707
- :src="currentChat?.avatar"
708
- :alt="currentChat?.name"
709
- class="chat-detail-avatar"
710
- />
143
+ <img :src="currentChat?.avatar" :alt="currentChat?.name" class="chat-detail-avatar" />
711
144
  <div class="chat-detail-name">{{ currentChat?.name }}</div>
712
145
  <div class="chat-detail-actions">
713
146
  <div class="chat-detail-action-item">查找聊天记录</div>
@@ -717,296 +150,164 @@
717
150
  </div>
718
151
  </div>
719
152
 
720
- <!-- 添加好友弹窗 -->
721
- <el-dialog
722
- v-model="addFriendDialogVisible"
723
- title="添加好友"
153
+ <!-- 弹窗组件 -->
154
+ <AddFriendDialog
155
+ v-model:visible="addFriendDialogVisible"
156
+ v-model:search-text="addFriendSearchText"
157
+ :loading="loadingAvailableUsers"
158
+ :users="filteredAvailableUsers"
159
+ @add="addFriend"
160
+ />
161
+
162
+ <CreateGroupDialog
163
+ v-model:visible="createGroupDialogVisible"
164
+ v-model:group-name="newGroupName"
165
+ v-model:group-remark="newGroupRemark"
166
+ :friend-list="filteredFriendList"
167
+ :selected-members="selectedMembersForCreate"
168
+ @toggle-member="toggleMemberForCreate"
169
+ @create="handleCreateGroup"
170
+ />
171
+
172
+ <GroupDetailDialog
173
+ v-if="currentSelectGroup"
174
+ v-model:visible="groupDetailVisible"
175
+ :current-select-group="currentSelectGroup"
176
+ :group-member-list="groupMemberList"
177
+ :my-username="myUsername"
178
+ :config="config"
179
+ :group-owner-username="groupOwnerUsername"
180
+ @edit-info="openEditGroupInfo"
181
+ @delete="handleDeleteGroup"
182
+ @invite="inviteMemberDialogVisible = true"
183
+ @edit-member-nick="openEditMemberNick"
184
+ @remove-member="handleRemoveMember"
185
+ @transfer-owner="handleTransferOwner"
186
+ @quit="handleQuitGroup"
187
+ />
188
+
189
+ <!-- 其他弹窗(暂时保留在原处,后续可继续拆分) -->
190
+ <!-- 编辑群信息弹窗 -->
191
+ <Dialog
192
+ v-if="currentSelectGroup"
193
+ :model-value="groupInfoVisible"
194
+ @update:model-value="groupInfoVisible = $event"
195
+ title="编辑群信息"
724
196
  width="500px"
725
- append-to-body
726
- >
727
- <div class="add-friend-search-wrapper">
728
- <el-input
729
- v-model="addFriendSearchText"
730
- placeholder="搜索用户"
731
- :prefix-icon="Search"
732
- class="add-friend-search-input"
733
- />
734
- </div>
735
- <div class="add-friend-users-list">
736
- <el-empty v-if="loadingAvailableUsers" description="加载中..." />
737
- <el-empty
738
- v-else-if="filteredAvailableUsers.length === 0"
739
- description="暂无用户"
740
- />
741
- <div
742
- v-else
743
- v-for="user in filteredAvailableUsers"
744
- :key="user.username"
745
- class="add-friend-user-item"
746
- >
747
- <div class="add-friend-user-info">
748
- <img
749
- :src="`https://api.dicebear.com/7.x/avataaars/svg?seed=${user.username}`"
750
- :alt="user.username"
751
- class="add-friend-user-avatar"
752
- />
753
- <div class="add-friend-user-name">{{ user.username }}</div>
754
- </div>
755
- <el-button type="primary" size="small" @click="addFriend(user)">添加</el-button>
756
- </div>
757
- </div>
758
- </el-dialog>
759
-
760
- <!-- 创建群聊弹窗 -->
761
- <el-dialog
762
- v-model="createGroupDialogVisible"
763
- title="创建群聊"
764
- width="600px"
765
- append-to-body
766
197
  >
767
- <div class="create-group-form">
198
+ <div class="edit-group-form">
768
199
  <div class="form-item">
769
200
  <label class="form-label">群名称</label>
770
- <el-input
771
- v-model="newGroupName"
201
+ <Input
202
+ v-model="editingGroupInfo.groupNickname"
772
203
  placeholder="请输入群名称"
773
- maxlength="50"
774
204
  />
775
205
  </div>
776
206
  <div class="form-item">
777
207
  <label class="form-label">群备注</label>
778
- <el-input
779
- v-model="newGroupRemark"
208
+ <Input
209
+ v-model="editingGroupInfo.remark"
780
210
  type="textarea"
781
- placeholder="请输入群备注(可选)"
782
211
  :rows="3"
783
- maxlength="200"
212
+ placeholder="请输入群备注"
784
213
  />
785
214
  </div>
786
215
  <div class="form-item">
787
- <label class="form-label">选择成员</label>
788
- <div class="member-selection">
789
- <el-empty v-if="filteredFriendList.length === 0" description="暂无好友可以添加" />
790
- <div
791
- v-else
792
- class="member-list"
793
- >
794
- <div
795
- v-for="friend in filteredFriendList"
796
- :key="friend.id"
797
- :class="[
798
- 'member-item',
799
- selectedMembersForCreate.some(m => m.id === friend.id) ? 'member-selected' : ''
800
- ]"
801
- @click="toggleMemberForCreate(friend)"
802
- >
803
- <img
804
- :src="friend.avatar"
805
- :alt="friend.name"
806
- class="member-avatar"
807
- />
808
- <span class="member-name">{{ friend.name }}</span>
809
- <el-icon v-if="selectedMembersForCreate.some(m => m.id === friend.id)" class="member-check"><CircleCheck /></el-icon>
810
- </div>
811
- </div>
812
- </div>
813
- </div>
814
- </div>
815
- <template #footer>
816
- <el-button @click="createGroupDialogVisible = false">取消</el-button>
817
- <el-button type="primary" @click="handleCreateGroup" :disabled="!newGroupName.trim()">创建</el-button>
818
- </template>
819
- </el-dialog>
820
-
821
- <!-- 群聊详情弹窗 -->
822
- <el-dialog
823
- v-if="currentSelectGroup"
824
- v-model="groupDetailVisible"
825
- :title="currentSelectGroup.name"
826
- width="500px"
827
- append-to-body
828
- >
829
- <div class="group-detail-content">
830
- <div class="group-info-section">
831
- <div class="group-info-item">
832
- <span class="info-label">群ID</span>
833
- <span class="info-value">{{ currentSelectGroup.groupId }}</span>
834
- </div>
835
- <div class="group-info-item">
836
- <span class="info-label">群主</span>
837
- <span class="info-value">{{ currentSelectGroup.owner }}</span>
838
- </div>
839
- <div class="group-info-item">
840
- <span class="info-label">成员数</span>
841
- <span class="info-value">{{ groupMemberList.length }}</span>
842
- </div>
843
- </div>
844
-
845
- <div class="group-actions-section" v-if="currentSelectGroup.owner === myUsername">
846
- <div class="section-header">
847
- <span class="section-title">群管理</span>
848
- </div>
849
- <div class="group-actions-list">
850
- <el-button type="primary" size="small" @click="openEditGroupInfo">
851
- <el-icon><Edit /></el-icon>编辑群信息
852
- </el-button>
853
- <el-button type="danger" size="small" @click="handleDeleteGroup">
854
- <el-icon><Delete /></el-icon>解散群聊
855
- </el-button>
856
- </div>
857
- </div>
858
-
859
- <div class="group-members-section">
860
- <div class="section-header">
861
- <span class="section-title">群成员</span>
862
- <el-button size="small" @click="inviteMemberDialogVisible = true">
863
- <el-icon><Plus /></el-icon>邀请成员
864
- </el-button>
865
- </div>
866
- <div class="group-members-list">
867
- <el-empty v-if="groupMemberList.length === 0" description="暂无成员" />
868
- <div
869
- v-else
870
- v-for="member in groupMemberList"
871
- :key="member.id || member.username"
872
- class="group-member-item"
873
- >
874
- <img
875
- :src="member.avatar ? `${props.config.api.baseUrl}${member.avatar}` : `https://api.dicebear.com/7.x/avataaars/svg?seed=${member.username}`"
876
- :alt="member.username"
877
- class="group-member-avatar"
878
- />
879
- <div class="group-member-info">
880
- <span class="group-member-name">{{ member.memberNick || member.username }}</span>
881
- <span v-if="member.username === currentSelectGroup.owner" class="group-member-badge">群主</span>
882
- </div>
883
- <div class="group-member-actions" v-if="currentSelectGroup.owner === myUsername && member.username !== myUsername">
884
- <el-button size="small" link @click="openEditMemberNick(member)">
885
- 改昵称
886
- </el-button>
887
- <el-button size="small" link type="danger" @click="handleRemoveMember(member.username)">
888
- 移除
889
- </el-button>
890
- <el-button size="small" link type="warning" @click="handleTransferOwner(member.username)">
891
- 转让
892
- </el-button>
893
- </div>
894
- <div class="group-member-actions" v-else-if="member.username === myUsername">
895
- <el-button size="small" link @click="openEditMemberNick(member)">
896
- 改昵称
897
- </el-button>
898
- </div>
899
- </div>
900
- </div>
216
+ <label class="form-label">群公告</label>
217
+ <Input
218
+ v-model="editingGroupInfo.notice"
219
+ type="textarea"
220
+ :rows="3"
221
+ placeholder="请输入群公告"
222
+ />
901
223
  </div>
902
224
  </div>
903
225
  <template #footer>
904
- <el-button @click="groupDetailVisible = false">关闭</el-button>
905
- <el-button type="danger" @click="handleQuitGroup">退出群聊</el-button>
906
- </template>
907
- </el-dialog>
908
-
909
- <!-- 编辑群信息弹窗 -->
910
- <el-dialog
911
- v-if="currentSelectGroup"
912
- v-model="groupInfoVisible"
913
- title="编辑群信息"
914
- width="500px"
915
- append-to-body
916
- >
917
- <el-form label-width="80px">
918
- <el-form-item label="群名称">
919
- <el-input v-model="editingGroupInfo.groupNickname" placeholder="请输入群名称" />
920
- </el-form-item>
921
- <el-form-item label="群备注">
922
- <el-input v-model="editingGroupInfo.remark" type="textarea" :rows="3" placeholder="请输入群备注" />
923
- </el-form-item>
924
- <el-form-item label="群公告">
925
- <el-input v-model="editingGroupInfo.notice" type="textarea" :rows="3" placeholder="请输入群公告" />
926
- </el-form-item>
927
- </el-form>
928
- <template #footer>
929
- <el-button @click="groupInfoVisible = false">取消</el-button>
930
- <el-button type="primary" @click="handleUpdateGroupInfo">保存</el-button>
226
+ <Button @click="groupInfoVisible = false">取消</Button>
227
+ <Button type="primary" @click="handleUpdateGroupInfo">保存</Button>
931
228
  </template>
932
- </el-dialog>
229
+ </Dialog>
933
230
 
934
231
  <!-- 编辑群成员昵称弹窗 -->
935
- <el-dialog
232
+ <Dialog
936
233
  v-if="currentSelectGroup"
937
- v-model="memberNickDialogVisible"
234
+ :model-value="memberNickDialogVisible"
235
+ @update:model-value="memberNickDialogVisible = $event"
938
236
  title="编辑群昵称"
939
237
  width="400px"
940
- append-to-body
941
238
  >
942
- <el-form label-width="80px">
943
- <el-form-item label="成员">
239
+ <div class="edit-member-nick-form">
240
+ <div class="form-item">
241
+ <label class="form-label">成员</label>
944
242
  <span>{{ editingMemberNick.targetUsername }}</span>
945
- </el-form-item>
946
- <el-form-item label="群昵称">
947
- <el-input v-model="editingMemberNick.memberNick" placeholder="请输入群昵称" />
948
- </el-form-item>
949
- </el-form>
243
+ </div>
244
+ <div class="form-item">
245
+ <label class="form-label">群昵称</label>
246
+ <Input
247
+ v-model="editingMemberNick.memberNick"
248
+ placeholder="请输入群昵称"
249
+ />
250
+ </div>
251
+ </div>
950
252
  <template #footer>
951
- <el-button @click="memberNickDialogVisible = false">取消</el-button>
952
- <el-button type="primary" @click="handleUpdateMemberNick">保存</el-button>
253
+ <Button @click="memberNickDialogVisible = false">取消</Button>
254
+ <Button type="primary" @click="handleUpdateMemberNick">保存</Button>
953
255
  </template>
954
- </el-dialog>
256
+ </Dialog>
955
257
 
956
258
  <!-- 消息已读成员弹窗 -->
957
- <el-dialog
259
+ <Dialog
958
260
  v-if="currentSelectGroup"
959
- v-model="msgReadUserDialogVisible"
261
+ :model-value="msgReadUserDialogVisible"
262
+ @update:model-value="msgReadUserDialogVisible = $event"
960
263
  title="消息已读状态"
961
264
  width="500px"
962
- append-to-body
963
265
  >
964
266
  <div class="msg-read-users-content">
965
267
  <div class="read-users-section">
966
268
  <div class="section-title">
967
- <el-icon><Check /></el-icon>已读 ({{ currentMsgReadList.readUserList.length }})
269
+ <Check />已读 ({{ currentMsgReadList.readUserList.length }})
968
270
  </div>
969
271
  <div class="users-list">
970
- <el-empty v-if="currentMsgReadList.readUserList.length === 0" description="暂无已读成员" />
272
+ <Empty v-if="currentMsgReadList.readUserList.length === 0" description="暂无已读成员" />
971
273
  <div v-else class="users-tag-list">
972
- <el-tag v-for="user in currentMsgReadList.readUserList" :key="user" class="user-tag">
274
+ <Tag v-for="user in currentMsgReadList.readUserList" :key="user" class="user-tag">
973
275
  {{ user }}
974
- </el-tag>
276
+ </Tag>
975
277
  </div>
976
278
  </div>
977
279
  </div>
978
280
  <div class="unread-users-section">
979
281
  <div class="section-title">
980
- <el-icon><Clock /></el-icon>未读 ({{ currentMsgReadList.unreadUserList.length }})
282
+ <Clock />未读 ({{ currentMsgReadList.unreadUserList.length }})
981
283
  </div>
982
284
  <div class="users-list">
983
- <el-empty v-if="currentMsgReadList.unreadUserList.length === 0" description="全部已读" />
285
+ <Empty v-if="currentMsgReadList.unreadUserList.length === 0" description="全部已读" />
984
286
  <div v-else class="users-tag-list">
985
- <el-tag v-for="user in currentMsgReadList.unreadUserList" :key="user" type="info" class="user-tag">
287
+ <Tag v-for="user in currentMsgReadList.unreadUserList" :key="user" type="info" class="user-tag">
986
288
  {{ user }}
987
- </el-tag>
289
+ </Tag>
988
290
  </div>
989
291
  </div>
990
292
  </div>
991
293
  </div>
992
294
  <template #footer>
993
- <el-button @click="msgReadUserDialogVisible = false">关闭</el-button>
295
+ <Button @click="msgReadUserDialogVisible = false">关闭</Button>
994
296
  </template>
995
- </el-dialog>
297
+ </Dialog>
996
298
 
997
299
  <!-- 邀请成员弹窗 -->
998
- <el-dialog
300
+ <Dialog
999
301
  v-if="currentSelectGroup"
1000
- v-model="inviteMemberDialogVisible"
302
+ :model-value="inviteMemberDialogVisible"
303
+ @update:model-value="inviteMemberDialogVisible = $event"
1001
304
  title="邀请成员"
1002
305
  width="500px"
1003
- append-to-body
1004
306
  >
1005
307
  <div class="invite-member-content">
1006
308
  <div class="invite-member-list">
1007
- <el-empty v-if="filteredFriendList.length === 0" description="暂无好友可以邀请" />
309
+ <Empty v-if="filteredFriendList.length === 0" description="暂无好友可以邀请" />
1008
310
  <div
1009
- v-else
1010
311
  class="member-list"
1011
312
  >
1012
313
  <div
@@ -1024,26 +325,26 @@
1024
325
  class="member-avatar"
1025
326
  />
1026
327
  <span class="member-name">{{ friend.name }}</span>
1027
- <el-icon v-if="selectedMembersForInvite.some(m => m.id === friend.id)" class="member-check"><CircleCheck /></el-icon>
328
+ <CircleCheck v-if="selectedMembersForInvite.some(m => m.id === friend.id)" class="member-check" />
1028
329
  </div>
1029
330
  </div>
1030
331
  </div>
1031
332
  </div>
1032
333
  <template #footer>
1033
- <el-button @click="inviteMemberDialogVisible = false">取消</el-button>
1034
- <el-button type="primary" @click="handleInviteMember" :disabled="selectedMembersForInvite.length === 0">
334
+ <Button @click="inviteMemberDialogVisible = false">取消</Button>
335
+ <Button type="primary" @click="handleInviteMember" :disabled="selectedMembersForInvite.length === 0">
1035
336
  邀请 {{ selectedMembersForInvite.length }} 人
1036
- </el-button>
337
+ </Button>
1037
338
  </template>
1038
- </el-dialog>
339
+ </Dialog>
1039
340
 
1040
341
  <!-- 用户设置弹窗 -->
1041
- <el-dialog
1042
- v-model="showSettingsDialog"
342
+ <Dialog
343
+ :model-value="showSettingsDialog"
344
+ @update:model-value="showSettingsDialog = $event"
1043
345
  title="个人设置"
1044
346
  width="560px"
1045
347
  :close-on-click-modal="false"
1046
- append-to-body
1047
348
  class="chat-settings-dialog"
1048
349
  >
1049
350
  <div class="chat-settings-container">
@@ -1060,7 +361,7 @@
1060
361
  class="chat-settings-avatar-edit"
1061
362
  @click="triggerAvatarUpload"
1062
363
  >
1063
- <el-icon :size="18" class="chat-settings-avatar-icon"><Camera /></el-icon>
364
+ <span :size="18" class="chat-settings-avatar-icon"><Camera /></span>
1064
365
  </div>
1065
366
  <input
1066
367
  ref="avatarInputRef"
@@ -1076,33 +377,61 @@
1076
377
  </div>
1077
378
  </div>
1078
379
 
1079
- <!-- 用户信息表单 -->
380
+ <!-- 主题选择 -->
1080
381
  <div class="chat-settings-form-section">
1081
382
  <div class="chat-settings-form-header">
1082
383
  <div class="chat-settings-form-title">
1083
- <el-icon><UserFilled /></el-icon>
1084
- 个人信息
384
+ <Setting />
385
+ 主题设置
1085
386
  </div>
1086
- <el-button
1087
- v-if="!isEditingUserInfo"
1088
- type="primary"
387
+ </div>
388
+ <div class="chat-settings-form">
389
+ <div class="theme-options">
390
+ <div
391
+ v-for="themeOption in THEME_OPTIONS_WITH_ICONS"
392
+ :key="themeOption.value"
393
+ :class="[
394
+ 'theme-option',
395
+ currentTheme === themeOption.value ? 'theme-option-active' : ''
396
+ ]"
397
+ @click="setTheme(themeOption.value)"
398
+ >
399
+ <span class="theme-option-icon">
400
+ <component :is="themeOption.iconComponent" />
401
+ </span>
402
+ <span class="theme-option-label">{{ themeOption.label }}</span>
403
+ <Check v-if="currentTheme === themeOption.value" class="theme-option-check" />
404
+ </div>
405
+ </div>
406
+ </div>
407
+ </div>
408
+
409
+ <!-- 用户信息表单 -->
410
+ <div class="chat-settings-form-section">
411
+ <div class="chat-settings-form-header">
412
+ <div class="chat-settings-form-title">
413
+ <UserFilled />
414
+ 个人信息
415
+ </div>
416
+ <Button
417
+ v-if="!isEditingUserInfo"
418
+ type="primary"
1089
419
  size="small"
1090
420
  @click="startEditUserInfo"
1091
421
  class="chat-settings-edit-btn"
1092
422
  >
1093
423
  编辑
1094
- </el-button>
424
+ </Button>
1095
425
  </div>
1096
426
 
1097
427
  <div class="chat-settings-form">
1098
428
  <!-- 昵称 -->
1099
429
  <div class="chat-settings-form-item">
1100
430
  <label class="chat-settings-form-label">昵称</label>
1101
- <el-input
431
+ <Input
1102
432
  v-if="isEditingUserInfo"
1103
433
  v-model="editingUserInfo.nickname"
1104
434
  placeholder="请输入昵称"
1105
- size="large"
1106
435
  />
1107
436
  <div
1108
437
  v-else
@@ -1115,11 +444,10 @@
1115
444
  <!-- 邮箱 -->
1116
445
  <div class="chat-settings-form-item">
1117
446
  <label class="chat-settings-form-label">邮箱</label>
1118
- <el-input
447
+ <Input
1119
448
  v-if="isEditingUserInfo"
1120
449
  v-model="editingUserInfo.email"
1121
450
  placeholder="请输入邮箱"
1122
- size="large"
1123
451
  />
1124
452
  <div
1125
453
  v-else
@@ -1132,11 +460,10 @@
1132
460
  <!-- 手机号 -->
1133
461
  <div class="chat-settings-form-item">
1134
462
  <label class="chat-settings-form-label">手机号</label>
1135
- <el-input
463
+ <Input
1136
464
  v-if="isEditingUserInfo"
1137
465
  v-model="editingUserInfo.phone"
1138
466
  placeholder="请输入手机号"
1139
- size="large"
1140
467
  />
1141
468
  <div
1142
469
  v-else
@@ -1149,13 +476,12 @@
1149
476
  <!-- 个人简介 -->
1150
477
  <div class="chat-settings-form-item">
1151
478
  <label class="chat-settings-form-label">个人简介</label>
1152
- <el-input
479
+ <Input
1153
480
  v-if="isEditingUserInfo"
1154
481
  v-model="editingUserInfo.bio"
1155
482
  type="textarea"
1156
483
  :rows="4"
1157
484
  placeholder="介绍一下自己吧..."
1158
- size="large"
1159
485
  />
1160
486
  <div
1161
487
  v-else
@@ -1167,18 +493,17 @@
1167
493
 
1168
494
  <!-- 编辑按钮 -->
1169
495
  <div v-if="isEditingUserInfo" class="chat-settings-form-actions">
1170
- <el-button size="default" @click="cancelEditUserInfo">取消</el-button>
1171
- <el-button
496
+ <Button @click="cancelEditUserInfo">取消</Button>
497
+ <Button
1172
498
  type="primary"
1173
- size="default"
1174
499
  :loading="savingUserInfo"
1175
500
  @click="saveUserInfo"
1176
- >保存更改</el-button>
501
+ >保存更改</Button>
1177
502
  </div>
1178
503
  </div>
1179
504
  </div>
1180
505
  </div>
1181
- </el-dialog>
506
+ </Dialog>
1182
507
 
1183
508
  <!-- 头像裁剪弹窗 -->
1184
509
  <AvatarCrop
@@ -1188,13 +513,13 @@
1188
513
  />
1189
514
 
1190
515
  <!-- 右键菜单 -->
1191
- <div
1192
- v-if="contextMenu.visible"
1193
- class="chat-context-menu"
1194
- :style="{ left: contextMenu.x + 'px', top: contextMenu.y + 'px' }"
516
+ <ContextMenu
517
+ :visible="contextMenu.visible"
518
+ :x="contextMenu.x"
519
+ :y="contextMenu.y"
1195
520
  >
1196
- <div class="chat-context-menu-item" @click="handleRemoveChat">删除聊天</div>
1197
- </div>
521
+ <div class="context-menu-item danger" @click="handleRemoveChat">删除聊天</div>
522
+ </ContextMenu>
1198
523
  </div>
1199
524
  </template>
1200
525
 
@@ -1205,34 +530,77 @@ import {
1205
530
  ChatDotRound,
1206
531
  Folder,
1207
532
  Picture,
1208
- ChatLineRound,
1209
- MoreFilled,
1210
- Search,
1211
533
  Plus,
534
+ User,
1212
535
  UserFilled,
1213
536
  Bell,
1214
537
  Setting,
1215
- Document,
1216
- Download,
1217
538
  CircleCheck,
1218
539
  Edit,
1219
540
  Delete,
1220
541
  Check,
1221
542
  Clock,
1222
543
  Close,
1223
- ArrowRight
1224
- } from '@element-plus/icons-vue'
544
+ ArrowRight,
545
+ ArrowDown,
546
+ Sunny,
547
+ Moon,
548
+ Monitor
549
+ } from './ui/icons/index.js'
1225
550
  import { useChat } from '../composables/useChat.js'
1226
- import { ElMessage, ElMessageBox } from 'element-plus'
551
+ import { useTheme } from '../composables/useTheme.js'
552
+ import { THEME_OPTIONS } from '../const/theme.js'
553
+ import { useMessageBox } from '../composables/useMessageBox.js'
1227
554
  import AvatarCrop from './AvatarCrop.vue'
1228
555
  import EmojiPicker from './EmojiPicker.vue'
1229
556
 
557
+ // 导入自定义 UI 组件
558
+ import { Button, Dialog, Input, Empty, Tag } from './ui/index.js'
559
+
560
+ // 导入新拆分的子组件
561
+ import Sidebar from './chat/Sidebar.vue'
562
+ import ContentList from './chat/ContentList.vue'
563
+ import MainArea from './chat/MainArea.vue'
564
+ import ChatWindow from './chat/ChatWindow.vue'
565
+ import GroupSidebar from './chat/GroupSidebar.vue'
566
+ import AddFriendDialog from './chat/dialogs/AddFriendDialog.vue'
567
+ import CreateGroupDialog from './chat/dialogs/CreateGroupDialog.vue'
568
+ import GroupDetailDialog from './chat/dialogs/GroupDetailDialog.vue'
569
+ import ContextMenu from './chat/ContextMenu.vue'
570
+
571
+ // 创建 Message 工具函数(简单实现)
572
+ const ElMessage = {
573
+ success: (text) => console.log('Success:', text),
574
+ warning: (text) => console.warn('Warning:', text),
575
+ error: (text) => console.error('Error:', text),
576
+ info: (text) => console.info('Info:', text)
577
+ }
578
+
579
+ const ElMessageBox = useMessageBox()
580
+
1230
581
  const props = defineProps({
1231
- config: { type: Object, required: true }
582
+ config: { type: Object, required: true },
583
+ groupOwnerUsername: { type: String, default: '' }
1232
584
  })
1233
585
 
1234
586
  const emit = defineEmits(['message', 'send', 'error', 'init'])
1235
587
 
588
+ // 初始化主题系统
589
+ const { currentTheme, appliedTheme, setTheme, THEMES } = useTheme()
590
+
591
+ // 图标映射
592
+ const iconMap = {
593
+ sun: Sunny,
594
+ moon: Moon,
595
+ monitor: Monitor
596
+ }
597
+
598
+ // 为 THEME_OPTIONS 绑定图标组件
599
+ const THEME_OPTIONS_WITH_ICONS = THEME_OPTIONS.map(opt => ({
600
+ ...opt,
601
+ iconComponent: iconMap[opt.icon]
602
+ }))
603
+
1236
604
  const {
1237
605
  myUsername,
1238
606
  myAvatar,
@@ -1256,6 +624,7 @@ const {
1256
624
  // ========== 群聊相关 ==========
1257
625
  activeTab,
1258
626
  groupList,
627
+ chatList: groupChatList, // 正在聊天的群聊
1259
628
  currentSelectGroup,
1260
629
  groupMsgList,
1261
630
  groupMemberList,
@@ -1272,6 +641,7 @@ const {
1272
641
  currentAllMessages,
1273
642
  groupInfoVisible,
1274
643
  editingGroupInfo,
644
+ isRemoveMemberMode,
1275
645
  editingMemberNick,
1276
646
  memberNickDialogVisible,
1277
647
  msgReadUserDialogVisible,
@@ -1302,7 +672,7 @@ const {
1302
672
  getGroupList,
1303
673
  getGroupHistory,
1304
674
  getGroupMembers,
1305
- selectGroup,
675
+ selectGroup: selectGroupFromComposable,
1306
676
  createGroup,
1307
677
  inviteGroupMember,
1308
678
  quitGroup,
@@ -1325,7 +695,8 @@ const {
1325
695
  updateSingleGroupField,
1326
696
  startEditField,
1327
697
  cancelEditField,
1328
- saveEditField
698
+ saveEditField,
699
+ setGroupToChatStatus
1329
700
 
1330
701
  } = useChat(props.config, (message) => {
1331
702
  emit('message', message)
@@ -1334,12 +705,8 @@ const {
1334
705
  const navTabs = computed(() => {
1335
706
  const tabs = [{ id: 'chat', icon: ChatDotRound, badge: 0 }]
1336
707
 
1337
- if (props.config.modules.friends) {
1338
- tabs.push({ id: 'friends', icon: UserFilled, badge: 0 })
1339
- }
1340
-
1341
- if (props.config.modules.groups) {
1342
- tabs.push({ id: 'groups', icon: UserFilled, badge: 0 })
708
+ if (props.config.modules.friends || props.config.modules.groups) {
709
+ tabs.push({ id: 'contacts', icon: UserFilled, badge: 0 })
1343
710
  }
1344
711
 
1345
712
  if (props.config.modules.apply) {
@@ -1353,6 +720,7 @@ const currentNavTab = ref('chat')
1353
720
  const currentChatId = ref(null)
1354
721
  const currentChat = ref(null)
1355
722
  const currentSelectedFriend = ref(null)
723
+ const currentSelectedGroup = ref(null)
1356
724
  const showChatDetail = ref(false)
1357
725
  const isEditingUserInfo = ref(false)
1358
726
  const editingUserInfo = ref({ nickname: '', email: '', phone: '', bio: '' })
@@ -1367,6 +735,59 @@ const pendingFiles = ref([])
1367
735
  const contextMenu = ref({ visible: false, x: 0, y: 0, chat: null })
1368
736
  const showEmojiPicker = ref(false)
1369
737
 
738
+ // 联系人列表折叠状态
739
+ const friendsCollapsed = ref(false)
740
+ const groupsCollapsed = ref(false)
741
+
742
+ const chatWindowRef = ref(null)
743
+
744
+ // 组合的聊天列表(好友+群聊)
745
+ const combinedChatList = computed(() => {
746
+ const friends = filteredUsers.value.map(u => ({ ...u, type: 'friend' }))
747
+
748
+ const groups = groupChatList.value.map(g => {
749
+ // 使用接口返回的 groupAvatarList 来生成 memberAvatars
750
+ const memberAvatars = (g.groupAvatarList || []).map((avatarPath, index) => ({
751
+ username: `member-${index}`,
752
+ avatar: `${props.config.api.baseUrl}${avatarPath}`
753
+ })).slice(0, 4)
754
+
755
+ // 获取最后一条消息
756
+ const lastMsg = g.lastMsgContent || '暂无消息'
757
+
758
+ // 获取最后消息发送者
759
+ const lastMsgSender = g.lastMsgSender || ''
760
+
761
+ // 获取未读数
762
+ const unread = g.unreadCount || 0
763
+
764
+ // 获取最后时间
765
+ const lastTime = g.lastMsgTime
766
+
767
+ return {
768
+ id: g.groupId,
769
+ groupId: g.groupId,
770
+ name: g.groupName,
771
+ avatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=${g.groupId}`,
772
+ memberAvatars,
773
+ lastMsg: lastMsg,
774
+ lastMsgSender: lastMsgSender,
775
+ lastTime: lastTime,
776
+ unread: unread,
777
+ type: 'group'
778
+ }
779
+ })
780
+
781
+ const result = [...friends, ...groups].sort((a, b) => {
782
+ // 按时间排序
783
+ if (!a.lastTime) return 1
784
+ if (!b.lastTime) return -1
785
+ return new Date(b.lastTime) - new Date(a.lastTime)
786
+ })
787
+
788
+ return result
789
+ })
790
+
1370
791
  // 群聊侧边栏相关
1371
792
  const showGroupSidebar = ref(false)
1372
793
  const muteGroup = ref(false)
@@ -1405,13 +826,53 @@ const hideContextMenu = () => {
1405
826
 
1406
827
  const handleRemoveChat = async () => {
1407
828
  if (!contextMenu.value.chat) return
1408
- const success = await setFriendToChatStatus(contextMenu.value.chat.id, 0)
1409
- if (success) {
1410
- if (currentChatId.value === contextMenu.value.chat.id) {
1411
- currentChatId.value = null
1412
- currentChat.value = null
829
+
830
+ const chat = contextMenu.value.chat
831
+
832
+ try {
833
+ // 确认对话框
834
+ const confirmText = chat.type === 'group'
835
+ ? `确定要删除与「${chat.name}」的群聊吗?`
836
+ : `确定要删除与「${chat.name}」的聊天吗?`
837
+
838
+ await ElMessageBox.confirm(confirmText, '删除聊天', {
839
+ confirmButtonText: '确定',
840
+ cancelButtonText: '取消',
841
+ type: 'warning'
842
+ })
843
+
844
+ let success = false
845
+
846
+ if (chat.type === 'group') {
847
+ // 群聊,使用 setGroupToChatStatus
848
+ success = await setGroupToChatStatus(chat.groupId, 0)
849
+ if (success) {
850
+ if (currentSelectGroup.value?.groupId === chat.groupId) {
851
+ currentSelectGroup.value = null
852
+ currentSelectedGroup.value = null
853
+ }
854
+ }
855
+ } else {
856
+ // 好友,使用 setFriendToChatStatus
857
+ success = await setFriendToChatStatus(chat.id, 0)
858
+ if (success) {
859
+ if (currentChatId.value === chat.id) {
860
+ currentChatId.value = null
861
+ currentChat.value = null
862
+ }
863
+ }
864
+ }
865
+
866
+ if (success) {
867
+ ElMessage.success('删除聊天成功')
868
+ }
869
+ } catch (error) {
870
+ if (error !== 'cancel') {
871
+ console.error('删除聊天失败:', error)
872
+ ElMessage.error('删除聊天失败')
1413
873
  }
1414
874
  }
875
+
1415
876
  hideContextMenu()
1416
877
  }
1417
878
 
@@ -1432,19 +893,18 @@ const selectChat = (chat) => {
1432
893
 
1433
894
  const selectFriend = (friend) => {
1434
895
  currentSelectedFriend.value = friend
896
+ currentSelectedGroup.value = null
1435
897
  currentChatId.value = null
1436
898
  currentChat.value = null
1437
899
  currentSelectGroup.value = null
1438
900
  }
1439
901
 
1440
- const selectGroupChat = (group) => {
1441
- currentSelectGroup.value = group
902
+ const selectGroup = (group) => {
903
+ currentSelectedGroup.value = group
904
+ currentSelectedFriend.value = null
1442
905
  currentChatId.value = null
1443
906
  currentChat.value = null
1444
- currentSelectedFriend.value = null
1445
- showChatDetail.value = false
1446
- groupDetailVisible.value = false
1447
- selectGroup(group)
907
+ currentSelectGroup.value = null
1448
908
  }
1449
909
 
1450
910
  const handleStartChat = async () => {
@@ -1462,6 +922,42 @@ const handleStartChat = async () => {
1462
922
  }
1463
923
  }
1464
924
 
925
+ const handleStartGroupChat = async () => {
926
+ if (!currentSelectedGroup.value) return
927
+
928
+ const success = await setGroupToChatStatus(currentSelectedGroup.value.groupId, 1)
929
+ if (success) {
930
+ currentNavTab.value = 'chat'
931
+ await nextTick()
932
+ const chatItem = combinedChatList.value.find(item => item.type === 'group' && item.groupId === currentSelectedGroup.value.groupId)
933
+ if (chatItem) {
934
+ selectGroupChat(chatItem)
935
+ } else {
936
+ // 如果在组合列表中没找到,直接选择
937
+ selectGroupChat(currentSelectedGroup.value)
938
+ }
939
+ currentSelectedGroup.value = null
940
+ }
941
+ }
942
+
943
+ const selectGroupChat = async (group) => {
944
+ currentSelectGroup.value = group
945
+ currentChatId.value = null
946
+ currentChat.value = null
947
+ currentSelectedFriend.value = null
948
+ currentSelectedGroup.value = null
949
+ showChatDetail.value = false
950
+ groupDetailVisible.value = false
951
+
952
+ // 如果群聊还不在聊天列表中,设置为聊天状态
953
+ const isInChatList = groupChatList.value.some(g => g.groupId === group.groupId)
954
+ if (!isInChatList) {
955
+ await setGroupToChatStatus(group.groupId, 1)
956
+ }
957
+
958
+ selectGroupFromComposable(group)
959
+ }
960
+
1465
961
  const handleAvatarClick = () => {
1466
962
  showSettingsDialog.value = true
1467
963
  }
@@ -1588,32 +1084,6 @@ const selectEmoji = (emoji) => {
1588
1084
  showEmojiPicker.value = false
1589
1085
  }
1590
1086
 
1591
- // 获取群聊头像网格样式
1592
- const getGroupAvatarGridStyle = (count, index) => {
1593
- const styles = {}
1594
-
1595
- if (count === 1) {
1596
- styles.width = '100%'
1597
- styles.height = '100%'
1598
- } else if (count === 2) {
1599
- styles.width = '50%'
1600
- styles.height = '100%'
1601
- } else if (count === 3) {
1602
- if (index === 0) {
1603
- styles.width = '100%'
1604
- styles.height = '50%'
1605
- } else {
1606
- styles.width = '50%'
1607
- styles.height = '50%'
1608
- }
1609
- } else {
1610
- styles.width = '50%'
1611
- styles.height = '50%'
1612
- }
1613
-
1614
- return styles
1615
- }
1616
-
1617
1087
  // ========== 群聊相关方法 ==========
1618
1088
  const toggleMemberForCreate = (friend) => {
1619
1089
  const index = selectedMembersForCreate.value.findIndex(m => m.id === friend.id)
@@ -1634,188 +1104,80 @@ const toggleMemberForInvite = (friend) => {
1634
1104
  }
1635
1105
 
1636
1106
  const handleCreateGroup = async () => {
1637
- const success = await createGroup()
1638
- if (success) {
1639
- ElMessage.success('群聊创建成功')
1640
- } else {
1641
- ElMessage.error('群聊创建失败')
1642
- }
1107
+ await createGroup()
1643
1108
  }
1644
1109
 
1645
1110
  const handleInviteMember = async () => {
1646
- const success = await inviteGroupMember()
1647
- if (success) {
1648
- ElMessage.success('邀请成功')
1649
- } else {
1650
- ElMessage.error('邀请失败')
1651
- }
1111
+ await inviteGroupMember()
1652
1112
  }
1653
1113
 
1654
1114
  const handleQuitGroup = async () => {
1655
- const confirm = await ElMessageBox.confirm('确定要退出群聊吗?', '提示', {
1656
- confirmButtonText: '确定',
1657
- cancelButtonText: '取消',
1658
- type: 'warning'
1659
- }).catch(() => {})
1660
-
1661
- if (confirm) {
1662
- const success = await quitGroup()
1663
- if (success) {
1664
- ElMessage.success('已退出群聊')
1665
- } else {
1666
- ElMessage.error('退出失败')
1667
- }
1668
- }
1669
- }
1115
+ await quitGroup()
1116
+ }
1670
1117
 
1671
1118
  // 处理解散群聊
1672
1119
  const handleDeleteGroup = async () => {
1673
- const confirm = await ElMessageBox.confirm(
1674
- '确定要解散该群聊吗?此操作不可恢复!',
1675
- '警告',
1676
- {
1677
- confirmButtonText: '确定',
1678
- cancelButtonText: '取消',
1679
- type: 'warning'
1680
- }
1681
- ).catch(() => {})
1682
-
1683
- if (confirm) {
1684
- const success = await deleteGroup()
1685
- if (success) {
1686
- ElMessage.success('群聊已解散')
1687
- showGroupSidebar.value = false
1688
- } else {
1689
- ElMessage.error('解散失败')
1690
- }
1691
- }
1120
+ await deleteGroup()
1121
+ showGroupSidebar.value = false
1692
1122
  }
1693
1123
 
1694
1124
  // 处理保存编辑字段
1695
1125
  const handleSaveEditField = async (field) => {
1696
- const loading = ElLoading.service({
1697
- lock: true,
1698
- text: '保存中...',
1699
- background: 'rgba(0, 0, 0, 0.7)',
1700
- })
1701
-
1702
1126
  try {
1703
- const success = await saveEditField(field)
1704
- if (success) {
1705
- ElMessage.success('保存成功')
1706
- } else {
1707
- ElMessage.error('保存失败')
1708
- }
1127
+ await saveEditField(field)
1709
1128
  } catch (error) {
1710
1129
  console.error('保存失败', error)
1711
- ElMessage.error('保存失败')
1712
- } finally {
1713
- loading.close()
1714
1130
  }
1715
1131
  }
1716
1132
 
1717
1133
  // 处理保存群成员昵称
1718
1134
  const handleUpdateMemberNick = async () => {
1719
- const loading = ElLoading.service({
1720
- lock: true,
1721
- text: '保存中...',
1722
- background: 'rgba(0, 0, 0, 0.7)',
1723
- })
1724
-
1725
1135
  try {
1726
1136
  const success = await updateMemberNick()
1727
1137
  if (success) {
1728
- ElMessage.success('群昵称修改成功')
1729
1138
  memberNickDialogVisible.value = false
1730
- } else {
1731
- ElMessage.error('修改失败')
1732
1139
  }
1733
1140
  } catch (error) {
1734
1141
  console.error('修改失败', error)
1735
- ElMessage.error('修改失败')
1736
- } finally {
1737
- loading.close()
1738
1142
  }
1739
1143
  }
1740
1144
 
1741
1145
  // 处理移除群成员
1742
1146
  const handleRemoveMember = async (username) => {
1743
- const confirm = await ElMessageBox.confirm(
1744
- `确定要移除成员 ${username} 吗?`,
1745
- '提示',
1746
- {
1747
- confirmButtonText: '确定',
1748
- cancelButtonText: '取消',
1749
- type: 'warning'
1750
- }
1751
- ).catch(() => {})
1752
-
1753
- if (confirm) {
1754
- const loading = ElLoading.service({
1755
- lock: true,
1756
- text: '移除中...',
1757
- background: 'rgba(0, 0, 0, 0.7)',
1758
- })
1759
-
1760
- try {
1761
- const success = await removeGroupMember(username)
1762
- if (success) {
1763
- ElMessage.success('成员已移除')
1764
- } else {
1765
- ElMessage.error('移除失败')
1147
+ try {
1148
+ await ElMessageBox.confirm(
1149
+ `确定要将成员“${username}”移出群聊吗?`,
1150
+ '移除成员',
1151
+ {
1152
+ confirmButtonText: '确定',
1153
+ cancelButtonText: '取消',
1154
+ type: 'warning'
1766
1155
  }
1767
- } catch (error) {
1156
+ )
1157
+ const success = await removeGroupMember(username, false)
1158
+ if (success) {
1159
+ ElMessage.success('移除成功')
1160
+ }
1161
+ } catch (error) {
1162
+ if (error !== 'cancel') {
1768
1163
  console.error('移除失败', error)
1769
1164
  ElMessage.error('移除失败')
1770
- } finally {
1771
- loading.close()
1772
1165
  }
1773
1166
  }
1774
1167
  }
1775
1168
 
1776
1169
  // 处理转让群主
1777
1170
  const handleTransferOwner = async (newOwnerUsername) => {
1778
- const confirm = await ElMessageBox.confirm(
1779
- `确定要将群主转让给 ${newOwnerUsername} 吗?`,
1780
- '提示',
1781
- {
1782
- confirmButtonText: '确定',
1783
- cancelButtonText: '取消',
1784
- type: 'warning'
1785
- }
1786
- ).catch(() => {})
1787
-
1788
- if (confirm) {
1789
- const loading = ElLoading.service({
1790
- lock: true,
1791
- text: '转让中...',
1792
- background: 'rgba(0, 0, 0, 0.7)',
1793
- })
1794
-
1795
- try {
1796
- const success = await transferGroupOwner(newOwnerUsername)
1797
- if (success) {
1798
- ElMessage.success('群主已转让')
1799
- } else {
1800
- ElMessage.error('转让失败')
1801
- }
1802
- } catch (error) {
1803
- console.error('转让失败', error)
1804
- ElMessage.error('转让失败')
1805
- } finally {
1806
- loading.close()
1807
- }
1171
+ try {
1172
+ await transferGroupOwner(newOwnerUsername)
1173
+ } catch (error) {
1174
+ console.error('转让失败', error)
1808
1175
  }
1809
1176
  }
1810
1177
 
1811
1178
  // 处理更新群信息
1812
1179
  const handleUpdateGroupInfo = async () => {
1813
- const success = await updateGroupInfo()
1814
- if (success) {
1815
- ElMessage.success('群信息已更新')
1816
- } else {
1817
- ElMessage.error('更新失败')
1818
- }
1180
+ await updateGroupInfo()
1819
1181
  }
1820
1182
 
1821
1183
 
@@ -1825,12 +1187,12 @@ const handleAvatarFileChange = (e) => {
1825
1187
  if (!file) return
1826
1188
 
1827
1189
  if (!file.type.startsWith('image/')) {
1828
- ElMessage.error('只能上传图片文件')
1190
+ console.error('只能上传图片文件')
1829
1191
  return
1830
1192
  }
1831
1193
 
1832
1194
  if (file.size > 5 * 1024 * 1024) {
1833
- ElMessage.error('图片大小不能超过 5MB')
1195
+ console.error('图片大小不能超过 5MB')
1834
1196
  return
1835
1197
  }
1836
1198
 
@@ -1851,15 +1213,11 @@ const handleAvatarCropConfirm = async ({ file }) => {
1851
1213
  const api = new ChatApi(props.config)
1852
1214
  const res = await api.uploadAvatar(file, myUsername)
1853
1215
  if (res.code === 200) {
1854
- ElMessage.success('头像上传成功')
1855
1216
  updateMyAvatar(res.data)
1856
1217
  resetAvatar()
1857
- } else {
1858
- ElMessage.error(res.msg || '头像上传失败')
1859
1218
  }
1860
1219
  } catch (error) {
1861
1220
  console.error(error)
1862
- ElMessage.error('头像上传失败')
1863
1221
  } finally {
1864
1222
  avatarUploading.value = false
1865
1223
  }
@@ -1893,14 +1251,10 @@ const saveUserInfo = async () => {
1893
1251
  try {
1894
1252
  const success = await updateUserInfo(editingUserInfo.value)
1895
1253
  if (success) {
1896
- ElMessage.success('保存成功')
1897
1254
  isEditingUserInfo.value = false
1898
- } else {
1899
- ElMessage.error('保存失败')
1900
1255
  }
1901
1256
  } catch (error) {
1902
1257
  console.error(error)
1903
- ElMessage.error('保存失败')
1904
1258
  } finally {
1905
1259
  savingUserInfo.value = false
1906
1260
  }
@@ -1934,1675 +1288,6 @@ onUnmounted(() => {
1934
1288
  })
1935
1289
  </script>
1936
1290
 
1937
- <style scoped>
1938
- /* ========== 基础样式 ========== */
1939
- .chat-panel {
1940
- display: flex;
1941
- height: 680px;
1942
- background: rgba(255, 255, 255, 0.7);
1943
- backdrop-filter: blur(20px);
1944
- -webkit-backdrop-filter: blur(20px);
1945
- overflow: hidden;
1946
- border-radius: 20px;
1947
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12),
1948
- 0 2px 12px rgba(0, 0, 0, 0.08);
1949
- border: 1px solid rgba(255, 255, 255, 0.3);
1950
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1951
- }
1952
-
1953
- /* ========== 左侧导航栏 ========== */
1954
- .chat-sidebar {
1955
- width: 64px;
1956
- display: flex;
1957
- flex-direction: column;
1958
- align-items: center;
1959
- gap: 8px;
1960
- background: rgba(249, 250, 251, 0.6);
1961
- backdrop-filter: blur(10px);
1962
- -webkit-backdrop-filter: blur(10px);
1963
- border-right: 1px solid rgba(229, 231, 235, 0.5);
1964
- }
1965
-
1966
- .sidebar-avatar {
1967
- margin-top: 16px;
1968
- margin-bottom: 16px;
1969
- cursor: pointer;
1970
- }
1971
-
1972
- .sidebar-avatar-img {
1973
- width: 40px;
1974
- height: 40px;
1975
- border-radius: 50%;
1976
- border: 2px solid #e5e7eb;
1977
- object-fit: cover;
1978
- }
1979
-
1980
- .sidebar-nav-item {
1981
- width: 40px;
1982
- height: 40px;
1983
- display: flex;
1984
- align-items: center;
1985
- justify-content: center;
1986
- cursor: pointer;
1987
- border-radius: 8px;
1988
- transition: all 0.2s;
1989
- position: relative;
1990
- }
1991
-
1992
- .sidebar-nav-item-active {
1993
- background-color: #dcfce7;
1994
- color: #07c160;
1995
- }
1996
-
1997
- .sidebar-nav-item-inactive {
1998
- color: #6b7280;
1999
- }
2000
-
2001
- .sidebar-nav-item-inactive:hover {
2002
- background-color: #f3f4f6;
2003
- }
2004
-
2005
- .sidebar-nav-badge {
2006
- position: absolute;
2007
- top: -4px;
2008
- right: -4px;
2009
- width: 16px;
2010
- height: 16px;
2011
- background-color: #ef4444;
2012
- border-radius: 50%;
2013
- font-size: 10px;
2014
- color: white;
2015
- display: flex;
2016
- align-items: center;
2017
- justify-content: center;
2018
- }
2019
-
2020
- .sidebar-spacer {
2021
- flex: 1;
2022
- }
2023
-
2024
- /* ========== 中间内容面板 ========== */
2025
- .chat-content-panel {
2026
- width: 288px;
2027
- background: rgba(245, 245, 245, 0.5);
2028
- backdrop-filter: blur(10px);
2029
- -webkit-backdrop-filter: blur(10px);
2030
- border-right: 1px solid rgba(229, 231, 235, 0.5);
2031
- display: flex;
2032
- flex-direction: column;
2033
- }
2034
-
2035
- .chat-search-bar {
2036
- padding: 12px;
2037
- }
2038
-
2039
- .chat-search-input {
2040
- width: 100%;
2041
- }
2042
-
2043
- .chat-content-scroll {
2044
- flex: 1;
2045
- overflow-y: auto;
2046
- min-height: 0;
2047
- }
2048
-
2049
- /* 聊天列表项 */
2050
- .chat-list-item {
2051
- display: flex;
2052
- align-items: center;
2053
- padding: 12px;
2054
- cursor: pointer;
2055
- transition: background-color 0.2s;
2056
- border-radius: 8px;
2057
- margin: 0 8px;
2058
- }
2059
-
2060
- .chat-list-item:hover {
2061
- background: rgba(229, 229, 229, 0.6);
2062
- }
2063
-
2064
- .chat-list-item-active {
2065
- background: rgba(7, 193, 96, 0.15);
2066
- }
2067
-
2068
- .chat-list-avatar-wrapper {
2069
- position: relative;
2070
- flex-shrink: 0;
2071
- }
2072
-
2073
- .chat-list-avatar {
2074
- width: 44px;
2075
- height: 44px;
2076
- border-radius: 50%;
2077
- object-fit: cover;
2078
- }
2079
-
2080
- /* 多方格群聊头像 */
2081
- .group-avatar-grid {
2082
- width: 44px;
2083
- height: 44px;
2084
- border-radius: 8px;
2085
- overflow: hidden;
2086
- display: flex;
2087
- flex-wrap: wrap;
2088
- background-color: #f3f4f6;
2089
- }
2090
-
2091
- .group-avatar-item {
2092
- overflow: hidden;
2093
- box-sizing: border-box;
2094
- border: 0.5px solid rgba(255, 255, 255, 0.3);
2095
- }
2096
-
2097
- .group-avatar-img {
2098
- width: 100%;
2099
- height: 100%;
2100
- object-fit: cover;
2101
- }
2102
-
2103
- .chat-list-online-indicator {
2104
- position: absolute;
2105
- bottom: 0;
2106
- right: 0;
2107
- width: 12px;
2108
- height: 12px;
2109
- background-color: #22c55e;
2110
- border-radius: 50%;
2111
- border: 2px solid white;
2112
- }
2113
-
2114
- .chat-list-online-indicator.chat-list-offline {
2115
- background-color: #9ca3af;
2116
- }
2117
-
2118
- .chat-list-info {
2119
- margin-left: 12px;
2120
- flex: 1;
2121
- overflow: hidden;
2122
- }
2123
-
2124
- .chat-list-header {
2125
- display: flex;
2126
- justify-content: space-between;
2127
- align-items: center;
2128
- }
2129
-
2130
- .chat-list-name {
2131
- font-weight: 500;
2132
- color: #1f2937;
2133
- font-size: 14px;
2134
- }
2135
-
2136
- .chat-list-time {
2137
- font-size: 12px;
2138
- color: #9ca3af;
2139
- }
2140
-
2141
- .chat-list-preview {
2142
- display: flex;
2143
- justify-content: space-between;
2144
- align-items: center;
2145
- margin-top: 4px;
2146
- }
2147
-
2148
- .chat-list-last-msg {
2149
- font-size: 12px;
2150
- color: #6b7280;
2151
- overflow: hidden;
2152
- text-overflow: ellipsis;
2153
- white-space: nowrap;
2154
- padding-right: 8px;
2155
- flex: 1;
2156
- }
2157
-
2158
- .chat-list-unread {
2159
- background-color: #ef4444;
2160
- color: white;
2161
- font-size: 10px;
2162
- border-radius: 9999px;
2163
- padding: 2px 6px;
2164
- min-width: 18px;
2165
- min-height: 18px;
2166
- height: 18px;
2167
- display: inline-flex;
2168
- align-items: center;
2169
- justify-content: center;
2170
- text-align: center;
2171
- line-height: 1;
2172
- }
2173
-
2174
- /* 添加好友 */
2175
- .add-friend-section {
2176
- padding: 12px;
2177
- }
2178
-
2179
- .add-friend-btn {
2180
- display: flex;
2181
- align-items: center;
2182
- gap: 8px;
2183
- padding: 8px;
2184
- border-radius: 8px;
2185
- cursor: pointer;
2186
- }
2187
-
2188
- .add-friend-btn:hover {
2189
- background-color: #e5e5e5;
2190
- }
2191
-
2192
- .add-friend-icon {
2193
- width: 44px;
2194
- height: 44px;
2195
- background-color: #07c160;
2196
- border-radius: 8px;
2197
- display: flex;
2198
- align-items: center;
2199
- justify-content: center;
2200
- }
2201
-
2202
- .add-friend-text {
2203
- font-size: 14px;
2204
- color: #1f2937;
2205
- }
2206
-
2207
- /* 好友申请 */
2208
- .friend-request-item {
2209
- display: flex;
2210
- align-items: center;
2211
- justify-content: space-between;
2212
- padding: 12px;
2213
- }
2214
-
2215
- .friend-request-item:hover {
2216
- background-color: #e5e5e5;
2217
- }
2218
-
2219
- .friend-request-info {
2220
- display: flex;
2221
- align-items: center;
2222
- }
2223
-
2224
- .friend-request-avatar {
2225
- width: 44px;
2226
- height: 44px;
2227
- border-radius: 50%;
2228
- object-fit: cover;
2229
- }
2230
-
2231
- .friend-request-details {
2232
- margin-left: 12px;
2233
- }
2234
-
2235
- .friend-request-username {
2236
- font-weight: 500;
2237
- color: #1f2937;
2238
- font-size: 14px;
2239
- }
2240
-
2241
- .friend-request-desc {
2242
- font-size: 12px;
2243
- color: #6b7280;
2244
- margin-top: 4px;
2245
- }
2246
-
2247
- /* ========== 右侧聊天区域 ========== */
2248
- .chat-main-area {
2249
- flex: 1;
2250
- display: flex;
2251
- flex-direction: column;
2252
- min-width: 0;
2253
- background: rgba(255, 255, 255, 0.5);
2254
- backdrop-filter: blur(10px);
2255
- -webkit-backdrop-filter: blur(10px);
2256
- }
2257
-
2258
- /* 好友信息 */
2259
- .friend-profile-area {
2260
- flex: 1;
2261
- display: flex;
2262
- flex-direction: column;
2263
- align-items: center;
2264
- justify-content: center;
2265
- padding: 32px;
2266
- background: rgba(245, 245, 245, 0.4);
2267
- }
2268
-
2269
- .profile-avatar {
2270
- width: 96px;
2271
- height: 96px;
2272
- border-radius: 50%;
2273
- object-fit: cover;
2274
- margin-bottom: 24px;
2275
- }
2276
-
2277
- .profile-name {
2278
- font-size: 20px;
2279
- font-weight: 500;
2280
- color: #1f2937;
2281
- margin-bottom: 8px;
2282
- }
2283
-
2284
- .profile-status {
2285
- display: flex;
2286
- align-items: center;
2287
- gap: 8px;
2288
- margin-bottom: 32px;
2289
- }
2290
-
2291
- .profile-status-dot {
2292
- width: 8px;
2293
- height: 8px;
2294
- border-radius: 50%;
2295
- }
2296
-
2297
- .profile-status-online {
2298
- background-color: #22c55e;
2299
- }
2300
-
2301
- .profile-status-offline {
2302
- background-color: #9ca3af;
2303
- }
2304
-
2305
- .profile-start-chat-btn {
2306
- width: 160px;
2307
- }
2308
-
2309
- /* 聊天窗口 */
2310
- .chat-window-area {
2311
- flex: 1;
2312
- display: flex;
2313
- flex-direction: column;
2314
- min-height: 0;
2315
- }
2316
-
2317
- .chat-window-header {
2318
- height: 56px;
2319
- border-bottom: 1px solid rgba(229, 231, 235, 0.5);
2320
- display: flex;
2321
- align-items: center;
2322
- justify-content: space-between;
2323
- padding: 0 16px;
2324
- background: rgba(255, 255, 255, 0.3);
2325
- backdrop-filter: blur(10px);
2326
- -webkit-backdrop-filter: blur(10px);
2327
- }
2328
-
2329
- .chat-window-title {
2330
- display: flex;
2331
- align-items: center;
2332
- gap: 12px;
2333
- }
2334
-
2335
- .chat-window-name {
2336
- font-weight: 500;
2337
- color: #1f2937;
2338
- }
2339
-
2340
- .chat-window-status {
2341
- font-size: 12px;
2342
- padding: 2px 8px;
2343
- border-radius: 4px;
2344
- }
2345
-
2346
- .chat-window-status-online {
2347
- background-color: #dcfce7;
2348
- color: #16a34a;
2349
- }
2350
-
2351
- .chat-window-status-offline {
2352
- background-color: #f3f4f6;
2353
- color: #6b7280;
2354
- }
2355
-
2356
- .chat-window-actions {
2357
- display: flex;
2358
- align-items: center;
2359
- gap: 12px;
2360
- color: #6b7280;
2361
- }
2362
-
2363
- .chat-action-icon {
2364
- cursor: pointer;
2365
- }
2366
-
2367
- .chat-action-icon:hover {
2368
- color: #374151;
2369
- }
2370
-
2371
- /* 消息容器 */
2372
- .chat-messages-container {
2373
- flex: 1;
2374
- overflow-y: auto;
2375
- padding: 16px;
2376
- background: rgba(245, 245, 245, 0.3);
2377
- min-height: 0;
2378
- }
2379
-
2380
- .chat-messages-container::-webkit-scrollbar {
2381
- width: 6px;
2382
- }
2383
-
2384
- .chat-messages-container::-webkit-scrollbar-thumb {
2385
- background: #ccc;
2386
- border-radius: 3px;
2387
- }
2388
-
2389
- .chat-messages-container::-webkit-scrollbar-track {
2390
- background: transparent;
2391
- }
2392
-
2393
- /* 消息样式 */
2394
- .message-wrapper {
2395
- display: flex;
2396
- margin-bottom: 24px;
2397
- align-items: flex-start;
2398
- }
2399
-
2400
- .message-self {
2401
- flex-direction: row-reverse;
2402
- }
2403
-
2404
- .message-other {
2405
- flex-direction: row;
2406
- }
2407
-
2408
- .message-avatar {
2409
- flex-shrink: 0;
2410
- }
2411
-
2412
- .message-avatar-img {
2413
- width: 40px;
2414
- height: 40px;
2415
- border-radius: 8px;
2416
- object-fit: cover;
2417
- }
2418
-
2419
- .message-content {
2420
- display: flex;
2421
- flex-direction: column;
2422
- max-width: 75%;
2423
- }
2424
-
2425
- .message-content-self {
2426
- margin-right: 12px;
2427
- align-items: flex-end;
2428
- }
2429
-
2430
- .message-content-other {
2431
- margin-left: 12px;
2432
- align-items: flex-start;
2433
- }
2434
-
2435
- .message-sender-name {
2436
- font-size: 12px;
2437
- color: #6b7280;
2438
- margin-bottom: 4px;
2439
- margin-left: 4px;
2440
- }
2441
-
2442
- .message-bubble-wrapper {
2443
- position: relative;
2444
- }
2445
-
2446
- .message-bubble {
2447
- padding: 8px 12px;
2448
- font-size: 14px;
2449
- word-break: break-all;
2450
- white-space: pre-wrap;
2451
- border-radius: 8px;
2452
- box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
2453
- }
2454
-
2455
- .message-bubble-self {
2456
- background-color: #95ec69;
2457
- color: #1f2937;
2458
- position: relative;
2459
- }
2460
-
2461
- .message-bubble-self::after {
2462
- content: '';
2463
- position: absolute;
2464
- right: -5px;
2465
- top: 10px;
2466
- width: 10px;
2467
- height: 10px;
2468
- background-color: #95ec69;
2469
- transform: rotate(45deg);
2470
- box-shadow: 2px -2px 2px 0 rgba(0, 0, 0, 0.05);
2471
- }
2472
-
2473
- .message-bubble-other {
2474
- background-color: white;
2475
- color: #1f2937;
2476
- position: relative;
2477
- }
2478
-
2479
- .message-bubble-other::after {
2480
- content: '';
2481
- position: absolute;
2482
- left: -5px;
2483
- top: 10px;
2484
- width: 10px;
2485
- height: 10px;
2486
- background-color: white;
2487
- transform: rotate(45deg);
2488
- box-shadow: -2px 2px 2px 0 rgba(0, 0, 0, 0.05);
2489
- }
2490
-
2491
- /* 图片消息 */
2492
- .message-image-bubble {
2493
- border-radius: 8px;
2494
- position: relative;
2495
- box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
2496
- cursor: pointer;
2497
- overflow: hidden;
2498
- max-width: 300px;
2499
- padding: 0;
2500
- }
2501
-
2502
- .message-image {
2503
- width: 100%;
2504
- height: auto;
2505
- display: block;
2506
- }
2507
-
2508
- .message-image-size {
2509
- position: absolute;
2510
- left: 4px;
2511
- bottom: 0;
2512
- color: white;
2513
- font-size: 10px;
2514
- }
2515
-
2516
- /* 文件消息 */
2517
- .message-file-bubble {
2518
- border-radius: 8px;
2519
- box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
2520
- cursor: pointer;
2521
- overflow: hidden;
2522
- min-width: 200px;
2523
- }
2524
-
2525
- .message-file-content {
2526
- display: flex;
2527
- align-items: center;
2528
- gap: 12px;
2529
- padding: 12px 16px;
2530
- }
2531
-
2532
- .message-file-icon {
2533
- width: 40px;
2534
- height: 40px;
2535
- display: flex;
2536
- align-items: center;
2537
- justify-content: center;
2538
- border-radius: 8px;
2539
- flex-shrink: 0;
2540
- }
2541
-
2542
- .message-bubble-self .message-file-icon {
2543
- color: #374151;
2544
- }
2545
-
2546
- .message-bubble-other .message-file-icon {
2547
- color: #6b7280;
2548
- }
2549
-
2550
- .message-file-info {
2551
- flex: 1;
2552
- min-width: 0;
2553
- }
2554
-
2555
- .message-file-name {
2556
- overflow: hidden;
2557
- text-overflow: ellipsis;
2558
- font-size: 14px;
2559
- font-weight: 500;
2560
- line-height: 1.2;
2561
- }
2562
-
2563
- .message-bubble-self .message-file-name {
2564
- color: #1f2937;
2565
- }
2566
-
2567
- .message-bubble-other .message-file-name {
2568
- color: #1f2937;
2569
- }
2570
-
2571
- .message-file-meta {
2572
- font-size: 12px;
2573
- margin-top: 4px;
2574
- display: flex;
2575
- align-items: center;
2576
- gap: 8px;
2577
- }
2578
-
2579
- .message-bubble-self .message-file-meta {
2580
- color: #4b5563;
2581
- }
2582
-
2583
- .message-bubble-other .message-file-meta {
2584
- color: #6b7280;
2585
- }
2586
-
2587
- .message-time {
2588
- font-size: 10px;
2589
- color: #9ca3af;
2590
- margin-top: 4px;
2591
- }
2592
-
2593
- .message-time-right {
2594
- text-align: right;
2595
- }
2596
-
2597
- .message-time-left {
2598
- text-align: left;
2599
- }
2600
-
2601
- /* 输入区域 */
2602
- .chat-input-area {
2603
- background: rgba(255, 255, 255, 0.6);
2604
- backdrop-filter: blur(10px);
2605
- -webkit-backdrop-filter: blur(10px);
2606
- border-top: 1px solid rgba(229, 231, 235, 0.5);
2607
- position: relative;
2608
- }
2609
-
2610
- /* 表情按钮包装 */
2611
- .emoji-button-wrapper {
2612
- position: relative;
2613
- display: inline-block;
2614
- }
2615
-
2616
- .pending-files-area {
2617
- padding: 8px 12px;
2618
- border-bottom: 1px solid #f3f4f6;
2619
- display: flex;
2620
- flex-wrap: wrap;
2621
- gap: 8px;
2622
- }
2623
-
2624
- .pending-file-item {
2625
- position: relative;
2626
- }
2627
-
2628
- .pending-image-wrapper {
2629
- position: relative;
2630
- width: 80px;
2631
- height: 80px;
2632
- border-radius: 8px;
2633
- overflow: hidden;
2634
- border: 1px solid #e5e7eb;
2635
- }
2636
-
2637
- .pending-image {
2638
- width: 100%;
2639
- height: 100%;
2640
- object-fit: cover;
2641
- }
2642
-
2643
- .pending-file-wrapper {
2644
- position: relative;
2645
- width: 96px;
2646
- height: 80px;
2647
- border-radius: 8px;
2648
- border: 1px solid #e5e7eb;
2649
- background-color: #f9fafb;
2650
- display: flex;
2651
- flex-direction: column;
2652
- align-items: center;
2653
- justify-content: center;
2654
- padding: 4px;
2655
- }
2656
-
2657
- .pending-file-icon {
2658
- color: #9ca3af;
2659
- font-size: 28px;
2660
- margin-bottom: 4px;
2661
- }
2662
-
2663
- .pending-file-name {
2664
- font-size: 10px;
2665
- color: #6b7280;
2666
- overflow: hidden;
2667
- text-overflow: ellipsis;
2668
- white-space: nowrap;
2669
- width: 100%;
2670
- text-align: center;
2671
- padding: 0 4px;
2672
- }
2673
-
2674
- .pending-file-remove-btn {
2675
- position: absolute;
2676
- top: 4px;
2677
- right: 4px;
2678
- width: 20px;
2679
- height: 20px;
2680
- background-color: rgba(0, 0, 0, 0.5);
2681
- color: white;
2682
- border-radius: 50%;
2683
- display: flex;
2684
- align-items: center;
2685
- justify-content: center;
2686
- cursor: pointer;
2687
- transition: background-color 0.2s;
2688
- font-size: 14px;
2689
- border: none;
2690
- }
2691
-
2692
- .pending-file-remove-btn:hover {
2693
- background-color: rgba(0, 0, 0, 0.7);
2694
- }
2695
-
2696
- .input-toolbar {
2697
- display: flex;
2698
- align-items: center;
2699
- padding: 12px;
2700
- gap: 8px;
2701
- }
2702
-
2703
- .input-toolbar-icon {
2704
- color: #6b7280;
2705
- cursor: pointer;
2706
- font-size: 20px;
2707
- }
2708
-
2709
- .input-toolbar-icon:hover {
2710
- color: #374151;
2711
- }
2712
-
2713
- .input-textarea-wrapper {
2714
- padding: 0 12px 12px;
2715
- }
2716
-
2717
- .message-input-textarea {
2718
- width: 100%;
2719
- resize: none;
2720
- border: none;
2721
- outline: none;
2722
- font-size: 14px;
2723
- height: 80px;
2724
- font-family: inherit;
2725
- }
2726
-
2727
- .input-send-wrapper {
2728
- display: flex;
2729
- justify-content: flex-end;
2730
- padding: 0 12px 12px;
2731
- }
2732
-
2733
- .send-message-btn {
2734
- background-color: #07c160;
2735
- border: none;
2736
- font-size: 14px;
2737
- padding: 8px 24px;
2738
- }
2739
-
2740
- .send-message-btn:hover {
2741
- background-color: #06ad56;
2742
- }
2743
-
2744
- .hidden-file-input {
2745
- display: none;
2746
- }
2747
-
2748
- /* 空状态 */
2749
- .chat-empty-state {
2750
- flex: 1;
2751
- display: flex;
2752
- align-items: center;
2753
- justify-content: center;
2754
- flex-direction: column;
2755
- background-color: #f5f5f5;
2756
- }
2757
-
2758
- .empty-state-icon {
2759
- color: #d1d5db;
2760
- margin-bottom: 8px;
2761
- }
2762
-
2763
- .empty-state-text {
2764
- color: #9ca3af;
2765
- font-size: 14px;
2766
- }
2767
-
2768
- /* ========== 右侧详情面板 ========== */
2769
- .chat-detail-panel {
2770
- width: 256px;
2771
- background: rgba(245, 245, 245, 0.5);
2772
- backdrop-filter: blur(10px);
2773
- -webkit-backdrop-filter: blur(10px);
2774
- border-left: 1px solid rgba(229, 231, 235, 0.5);
2775
- display: flex;
2776
- flex-direction: column;
2777
- }
2778
-
2779
- .chat-detail-header {
2780
- height: 56px;
2781
- display: flex;
2782
- align-items: center;
2783
- justify-content: center;
2784
- border-bottom: 1px solid rgba(229, 231, 235, 0.5);
2785
- font-weight: 500;
2786
- color: #374151;
2787
- font-size: 14px;
2788
- }
2789
-
2790
- .chat-detail-content {
2791
- flex: 1;
2792
- padding: 16px;
2793
- }
2794
-
2795
- .chat-detail-profile {
2796
- display: flex;
2797
- flex-direction: column;
2798
- align-items: center;
2799
- }
2800
-
2801
- .chat-detail-avatar {
2802
- width: 80px;
2803
- height: 80px;
2804
- border-radius: 50%;
2805
- object-fit: cover;
2806
- }
2807
-
2808
- .chat-detail-name {
2809
- margin-top: 12px;
2810
- font-weight: 500;
2811
- color: #1f2937;
2812
- font-size: 18px;
2813
- }
2814
-
2815
- .chat-detail-actions {
2816
- margin-top: 24px;
2817
- width: 100%;
2818
- }
2819
-
2820
- .chat-detail-action-item {
2821
- padding: 12px;
2822
- border-bottom: 1px solid #f3f4f6;
2823
- cursor: pointer;
2824
- background-color: white;
2825
- font-size: 14px;
2826
- color: #374151;
2827
- }
2828
-
2829
- .chat-detail-action-item:hover {
2830
- background-color: #f9fafb;
2831
- }
2832
-
2833
- .chat-detail-action-item:first-child {
2834
- border-radius: 8px 8px 0 0;
2835
- }
2836
-
2837
- .chat-detail-action-item:last-child {
2838
- border-bottom: none;
2839
- border-radius: 0 0 8px 8px;
2840
- }
2841
-
2842
- /* ========== 添加好友搜索 ========== */
2843
- .add-friend-search-wrapper {
2844
- margin-bottom: 16px;
2845
- }
2846
-
2847
- .add-friend-search-input {
2848
- width: 100%;
2849
- }
2850
-
2851
- .add-friend-users-list {
2852
- max-height: 400px;
2853
- overflow-y: auto;
2854
- }
2855
-
2856
- .add-friend-user-item {
2857
- display: flex;
2858
- align-items: center;
2859
- justify-content: space-between;
2860
- padding: 12px;
2861
- margin-bottom: 8px;
2862
- border-radius: 8px;
2863
- transition: background-color 0.2s;
2864
- }
2865
-
2866
- .add-friend-user-item:hover {
2867
- background-color: #f9fafb;
2868
- }
2869
-
2870
- .add-friend-user-info {
2871
- display: flex;
2872
- align-items: center;
2873
- }
2874
-
2875
- .add-friend-user-avatar {
2876
- width: 40px;
2877
- height: 40px;
2878
- border-radius: 50%;
2879
- object-fit: cover;
2880
- }
2881
-
2882
- .add-friend-user-name {
2883
- margin-left: 12px;
2884
- font-weight: 500;
2885
- color: #1f2937;
2886
- font-size: 14px;
2887
- }
2888
-
2889
- /* ========== 设置弹窗 ========== */
2890
- .chat-settings-dialog :deep(.el-dialog) {
2891
- border-radius: 16px;
2892
- overflow: hidden;
2893
- }
2894
-
2895
- .chat-settings-dialog :deep(.el-dialog__header) {
2896
- padding: 24px 24px 0;
2897
- margin: 0;
2898
- }
2899
-
2900
- .chat-settings-dialog :deep(.el-dialog__title) {
2901
- font-size: 20px;
2902
- font-weight: 600;
2903
- color: #1f2937;
2904
- }
2905
-
2906
- .chat-settings-container {
2907
- display: flex;
2908
- flex-direction: column;
2909
- }
2910
-
2911
- .chat-settings-avatar-section {
2912
- display: flex;
2913
- flex-direction: column;
2914
- align-items: center;
2915
- margin-bottom: 32px;
2916
- padding-bottom: 24px;
2917
- border-bottom: 1px solid #f3f4f6;
2918
- }
2919
-
2920
- .chat-settings-avatar-wrapper {
2921
- position: relative;
2922
- margin-bottom: 16px;
2923
- }
2924
-
2925
- .chat-settings-avatar {
2926
- width: 112px;
2927
- height: 112px;
2928
- border-radius: 50%;
2929
- object-fit: cover;
2930
- border: 4px solid white;
2931
- box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
2932
- }
2933
-
2934
- .chat-settings-avatar-edit {
2935
- position: absolute;
2936
- bottom: -4px;
2937
- right: -4px;
2938
- width: 40px;
2939
- height: 40px;
2940
- background-color: #07c160;
2941
- border-radius: 50%;
2942
- display: flex;
2943
- align-items: center;
2944
- justify-content: center;
2945
- cursor: pointer;
2946
- transition: background-color 0.2s;
2947
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
2948
- }
2949
-
2950
- .chat-settings-avatar-edit:hover {
2951
- background-color: #06ad56;
2952
- }
2953
-
2954
- .chat-settings-avatar-icon {
2955
- color: white;
2956
- }
2957
-
2958
- .hidden-avatar-input {
2959
- display: none;
2960
- }
2961
-
2962
- .chat-settings-user-display {
2963
- text-align: center;
2964
- }
2965
-
2966
- .chat-settings-nickname {
2967
- font-weight: 600;
2968
- color: #1f2937;
2969
- font-size: 20px;
2970
- }
2971
-
2972
- .chat-settings-username {
2973
- font-size: 14px;
2974
- color: #6b7280;
2975
- margin-top: 4px;
2976
- }
2977
-
2978
- .chat-settings-form-section {
2979
- gap: 20px;
2980
- }
2981
-
2982
- .chat-settings-form-header {
2983
- display: flex;
2984
- align-items: center;
2985
- justify-content: space-between;
2986
- margin-bottom: 8px;
2987
- }
2988
-
2989
- .chat-settings-form-title {
2990
- color: #374151;
2991
- font-weight: 600;
2992
- display: flex;
2993
- align-items: center;
2994
- gap: 8px;
2995
- font-size: 14px;
2996
- }
2997
-
2998
- .chat-settings-edit-btn {
2999
- border-radius: 9999px;
3000
- }
3001
-
3002
- .chat-settings-form {
3003
- background-color: #f9fafb;
3004
- border-radius: 16px;
3005
- padding: 24px;
3006
- gap: 20px;
3007
- display: flex;
3008
- flex-direction: column;
3009
- }
3010
-
3011
- .chat-settings-form-item {
3012
- display: flex;
3013
- flex-direction: column;
3014
- }
3015
-
3016
- .chat-settings-form-label {
3017
- display: block;
3018
- font-size: 14px;
3019
- color: #4b5563;
3020
- margin-bottom: 8px;
3021
- font-weight: 500;
3022
- }
3023
-
3024
- .chat-settings-form-value {
3025
- color: #1f2937;
3026
- background-color: white;
3027
- border-radius: 8px;
3028
- padding: 12px 16px;
3029
- border: 1px solid #e5e7eb;
3030
- font-size: 14px;
3031
- }
3032
-
3033
- .chat-settings-form-value.bio-value {
3034
- min-height: 80px;
3035
- }
3036
-
3037
- .chat-settings-form-actions {
3038
- display: flex;
3039
- gap: 12px;
3040
- justify-content: flex-end;
3041
- padding-top: 8px;
3042
- }
3043
-
3044
- /* ========== 右键菜单 ========== */
3045
- .chat-context-menu {
3046
- position: fixed;
3047
- background: rgba(255, 255, 255, 0.9);
3048
- backdrop-filter: blur(20px);
3049
- -webkit-backdrop-filter: blur(20px);
3050
- border-radius: 12px;
3051
- box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
3052
- border: 1px solid rgba(255, 255, 255, 0.3);
3053
- padding: 4px 0;
3054
- z-index: 1000;
3055
- }
3056
-
3057
- .chat-context-menu-item {
3058
- padding: 8px 16px;
3059
- cursor: pointer;
3060
- font-size: 14px;
3061
- color: #374151;
3062
- margin: 2px 4px;
3063
- border-radius: 8px;
3064
- transition: background-color 0.2s;
3065
- }
3066
-
3067
- .chat-context-menu-item:hover {
3068
- background-color: rgba(243, 244, 246, 0.8);
3069
- }
3070
-
3071
- /* ========== 群聊相关样式 ========== */
3072
- .create-group-form {
3073
- display: flex;
3074
- flex-direction: column;
3075
- gap: 20px;
3076
- }
3077
-
3078
- .form-item {
3079
- display: flex;
3080
- flex-direction: column;
3081
- gap: 8px;
3082
- }
3083
-
3084
- .form-label {
3085
- font-size: 14px;
3086
- font-weight: 500;
3087
- color: #374151;
3088
- }
3089
-
3090
- .member-selection {
3091
- border: 1px solid #e5e7eb;
3092
- border-radius: 8px;
3093
- padding: 12px;
3094
- background-color: #f9fafb;
3095
- }
3096
-
3097
- .member-list {
3098
- display: grid;
3099
- grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
3100
- gap: 12px;
3101
- }
3102
-
3103
- .member-item {
3104
- display: flex;
3105
- flex-direction: column;
3106
- align-items: center;
3107
- padding: 12px 8px;
3108
- border-radius: 8px;
3109
- cursor: pointer;
3110
- transition: background-color 0.2s;
3111
- border: 2px solid transparent;
3112
- }
3113
-
3114
- .member-item:hover {
3115
- background-color: rgba(243, 244, 246, 0.8);
3116
- }
3117
-
3118
- .member-item.member-selected {
3119
- border-color: #07c160;
3120
- background-color: rgba(7, 193, 96, 0.05);
3121
- }
3122
-
3123
- .member-item img {
3124
- width: 56px;
3125
- height: 56px;
3126
- border-radius: 50%;
3127
- object-fit: cover;
3128
- margin-bottom: 8px;
3129
- }
3130
-
3131
- .member-item span {
3132
- font-size: 13px;
3133
- color: #374151;
3134
- text-align: center;
3135
- overflow: hidden;
3136
- text-overflow: ellipsis;
3137
- white-space: nowrap;
3138
- max-width: 100%;
3139
- }
3140
-
3141
- .member-check {
3142
- color: #07c160;
3143
- margin-top: 4px;
3144
- }
3145
-
3146
- .group-detail-content {
3147
- display: flex;
3148
- flex-direction: column;
3149
- gap: 16px;
3150
- }
3151
-
3152
- .group-info-section {
3153
- display: flex;
3154
- flex-direction: column;
3155
- gap: 12px;
3156
- }
3157
-
3158
- .group-info-item {
3159
- display: flex;
3160
- justify-content: space-between;
3161
- align-items: center;
3162
- padding: 8px 0;
3163
- }
3164
-
3165
- .info-label {
3166
- font-size: 14px;
3167
- color: #6b7280;
3168
- }
3169
-
3170
- .info-value {
3171
- font-size: 14px;
3172
- color: #1f2937;
3173
- font-weight: 500;
3174
- }
3175
-
3176
- .group-members-section {
3177
- display: flex;
3178
- flex-direction: column;
3179
- gap: 12px;
3180
- }
3181
-
3182
- .section-header {
3183
- display: flex;
3184
- justify-content: space-between;
3185
- align-items: center;
3186
- }
3187
-
3188
- .section-title {
3189
- font-size: 14px;
3190
- font-weight: 500;
3191
- color: #374151;
3192
- }
3193
-
3194
- .group-members-list {
3195
- display: flex;
3196
- flex-direction: column;
3197
- gap: 8px;
3198
- max-height: 300px;
3199
- overflow-y: auto;
3200
- }
3201
-
3202
- .group-member-item {
3203
- display: flex;
3204
- align-items: center;
3205
- gap: 12px;
3206
- padding: 8px 12px;
3207
- border-radius: 8px;
3208
- background-color: white;
3209
- transition: background-color 0.2s;
3210
- }
3211
-
3212
- .group-member-item:hover {
3213
- background-color: #f9fafb;
3214
- }
3215
-
3216
- .group-member-avatar {
3217
- width: 40px;
3218
- height: 40px;
3219
- border-radius: 50%;
3220
- object-fit: cover;
3221
- flex-shrink: 0;
3222
- }
3223
-
3224
- .group-member-name {
3225
- font-size: 14px;
3226
- color: #1f2937;
3227
- flex: 1;
3228
- overflow: hidden;
3229
- text-overflow: ellipsis;
3230
- white-space: nowrap;
3231
- }
3232
-
3233
- /* ========== 群聊侧边栏样式 ========== */
3234
- .group-sidebar {
3235
- width: 320px;
3236
- background-color: #f5f5f5;
3237
- border-left: 1px solid #e5e7eb;
3238
- display: flex;
3239
- flex-direction: column;
3240
- height: 100%;
3241
- overflow: hidden;
3242
- }
3243
-
3244
- .slide-enter-active,
3245
- .slide-leave-active {
3246
- transition: transform 0.3s ease;
3247
- }
3248
-
3249
- .slide-enter-from,
3250
- .slide-leave-to {
3251
- transform: translateX(100%);
3252
- }
3253
-
3254
- .group-sidebar-header {
3255
- height: 56px;
3256
- display: flex;
3257
- justify-content: space-between;
3258
- align-items: center;
3259
- padding: 0 16px;
3260
- border-bottom: 1px solid #e5e7eb;
3261
- background-color: white;
3262
- }
3263
-
3264
- .group-sidebar-title {
3265
- font-size: 16px;
3266
- font-weight: 500;
3267
- color: #1f2937;
3268
- }
3269
-
3270
- .group-sidebar-close {
3271
- width: 32px;
3272
- height: 32px;
3273
- display: flex;
3274
- align-items: center;
3275
- justify-content: center;
3276
- border-radius: 50%;
3277
- cursor: pointer;
3278
- transition: background-color 0.2s;
3279
- color: #6b7280;
3280
- }
3281
-
3282
- .group-sidebar-close:hover {
3283
- background-color: #f3f4f6;
3284
- color: #374151;
3285
- }
3286
-
3287
- .group-sidebar-content {
3288
- flex: 1;
3289
- overflow-y: auto;
3290
- padding: 16px;
3291
- }
3292
-
3293
- .group-members-avatar-section {
3294
- margin-bottom: 16px;
3295
- }
3296
-
3297
- .group-members-grid {
3298
- display: grid;
3299
- grid-template-columns: repeat(4, 1fr);
3300
- gap: 16px;
3301
- }
3302
-
3303
- .group-member-avatar-item {
3304
- display: flex;
3305
- flex-direction: column;
3306
- align-items: center;
3307
- cursor: pointer;
3308
- padding: 4px;
3309
- border-radius: 8px;
3310
- transition: background-color 0.2s;
3311
- }
3312
-
3313
- .group-member-avatar-item:hover {
3314
- background-color: rgba(0, 0, 0, 0.05);
3315
- }
3316
-
3317
- .group-member-avatar-small {
3318
- width: 48px;
3319
- height: 48px;
3320
- border-radius: 50%;
3321
- object-fit: cover;
3322
- margin-bottom: 4px;
3323
- }
3324
-
3325
- .group-member-add-icon {
3326
- width: 48px;
3327
- height: 48px;
3328
- border-radius: 50%;
3329
- background-color: #f3f4f6;
3330
- display: flex;
3331
- align-items: center;
3332
- justify-content: center;
3333
- color: #6b7280;
3334
- margin-bottom: 4px;
3335
- }
3336
-
3337
- .group-member-nickname {
3338
- font-size: 12px;
3339
- color: #6b7280;
3340
- text-align: center;
3341
- overflow: hidden;
3342
- text-overflow: ellipsis;
3343
- white-space: nowrap;
3344
- max-width: 100%;
3345
- }
3346
-
3347
- .group-divider {
3348
- height: 12px;
3349
- background-color: transparent;
3350
- border-top: 1px solid #e5e7eb;
3351
- margin: 16px 0;
3352
- }
3353
-
3354
- .group-settings-section {
3355
- display: flex;
3356
- flex-direction: column;
3357
- gap: 4px;
3358
- }
3359
-
3360
- .group-setting-item {
3361
- display: flex;
3362
- justify-content: space-between;
3363
- align-items: center;
3364
- padding: 14px 16px;
3365
- background-color: white;
3366
- cursor: pointer;
3367
- transition: background-color 0.2s;
3368
- border-radius: 8px;
3369
- margin-bottom: 4px;
3370
- }
3371
-
3372
- .group-setting-item:hover {
3373
- background-color: #f9fafb;
3374
- }
3375
-
3376
- .group-setting-item.danger {
3377
- color: #ef4444;
3378
- margin-top: 8px;
3379
- }
3380
-
3381
- .group-setting-item.danger:hover {
3382
- background-color: #fef2f2;
3383
- }
3384
-
3385
- .group-setting-label {
3386
- font-size: 14px;
3387
- color: #1f2937;
3388
- }
3389
-
3390
- .group-setting-item.danger .group-setting-label {
3391
- color: #ef4444;
3392
- }
3393
-
3394
- .group-setting-value {
3395
- display: flex;
3396
- align-items: center;
3397
- gap: 8px;
3398
- color: #6b7280;
3399
- font-size: 14px;
3400
- }
3401
-
3402
- .group-setting-value-text {
3403
- max-width: 160px;
3404
- overflow: hidden;
3405
- text-overflow: ellipsis;
3406
- white-space: nowrap;
3407
- }
3408
-
3409
- .group-setting-value-wrapper {
3410
- display: flex;
3411
- flex: 1;
3412
- justify-content: flex-end;
3413
- }
3414
-
3415
- .group-edit-wrapper {
3416
- width: 100%;
3417
- display: flex;
3418
- flex-direction: column;
3419
- gap: 12px;
3420
- }
3421
-
3422
- .group-edit-buttons {
3423
- display: flex;
3424
- justify-content: flex-end;
3425
- gap: 8px;
3426
- }
3427
-
3428
- .group-input-edit {
3429
- flex: 1;
3430
- }
3431
-
3432
- .group-edit-actions {
3433
- display: flex;
3434
- gap: 8px;
3435
- justify-content: flex-end;
3436
- margin-top: 8px;
3437
- }
3438
-
3439
- /* ========== 消息已读成员弹窗样式 ========== */
3440
- .msg-read-users-content {
3441
- display: flex;
3442
- flex-direction: column;
3443
- gap: 20px;
3444
- }
3445
-
3446
- .read-users-section,
3447
- .unread-users-section {
3448
- display: flex;
3449
- flex-direction: column;
3450
- gap: 12px;
3451
- }
3452
-
3453
- .users-list {
3454
- display: flex;
3455
- flex-direction: column;
3456
- gap: 8px;
3457
- }
3458
-
3459
- .users-tag-list {
3460
- display: flex;
3461
- flex-wrap: wrap;
3462
- gap: 8px;
3463
- }
3464
-
3465
- .user-tag {
3466
- padding: 6px 12px;
3467
- background-color: #f3f4f6;
3468
- border-radius: 9999px;
3469
- font-size: 13px;
3470
- color: #374151;
3471
-
3472
- grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
3473
- gap: 12px;
3474
- }
3475
-
3476
- .member-item {
3477
- display: flex;
3478
- align-items: center;
3479
- gap: 8px;
3480
- padding: 8px 12px;
3481
- border-radius: 8px;
3482
- cursor: pointer;
3483
- transition: all 0.2s;
3484
- border: 2px solid transparent;
3485
- background-color: white;
3486
- }
3487
-
3488
- .member-item:hover {
3489
- background-color: #f3f4f6;
3490
- }
3491
-
3492
- .member-selected {
3493
- background-color: rgba(7, 193, 96, 0.1);
3494
- border-color: #07c160;
3495
- }
3496
-
3497
- .member-avatar {
3498
- width: 40px;
3499
- height: 40px;
3500
- border-radius: 50%;
3501
- object-fit: cover;
3502
- }
3503
-
3504
- .member-name {
3505
- flex: 1;
3506
- font-size: 14px;
3507
- color: #374151;
3508
- overflow: hidden;
3509
- text-overflow: ellipsis;
3510
- white-space: nowrap;
3511
- }
3512
-
3513
- .member-check {
3514
- color: #07c160;
3515
- font-size: 20px;
3516
- }
3517
-
3518
- .group-detail-content {
3519
- display: flex;
3520
- flex-direction: column;
3521
- gap: 20px;
3522
- }
3523
-
3524
- .group-info-section {
3525
- display: flex;
3526
- flex-direction: column;
3527
- gap: 12px;
3528
- }
3529
-
3530
- .group-info-item {
3531
- display: flex;
3532
- justify-content: space-between;
3533
- align-items: center;
3534
- padding: 12px;
3535
- background-color: #f9fafb;
3536
- border-radius: 8px;
3537
- }
3538
-
3539
- .info-label {
3540
- font-size: 14px;
3541
- color: #6b7280;
3542
- }
3543
-
3544
- .info-value {
3545
- font-size: 14px;
3546
- color: #374151;
3547
- font-weight: 500;
3548
- }
3549
-
3550
- .group-members-section {
3551
- display: flex;
3552
- flex-direction: column;
3553
- gap: 12px;
3554
- }
3555
-
3556
- .section-header {
3557
- display: flex;
3558
- justify-content: space-between;
3559
- align-items: center;
3560
- }
3561
-
3562
- .section-title {
3563
- font-size: 14px;
3564
- font-weight: 500;
3565
- color: #374151;
3566
- }
3567
-
3568
- .group-members-list {
3569
- display: grid;
3570
- grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
3571
- gap: 12px;
3572
- max-height: 300px;
3573
- overflow-y: auto;
3574
- }
3575
-
3576
- .group-member-item {
3577
- display: flex;
3578
- flex-direction: column;
3579
- align-items: center;
3580
- gap: 8px;
3581
- padding: 12px;
3582
- border-radius: 8px;
3583
- background-color: #f9fafb;
3584
- text-align: center;
3585
- }
3586
-
3587
- .group-member-avatar {
3588
- width: 50px;
3589
- height: 50px;
3590
- border-radius: 50%;
3591
- object-fit: cover;
3592
- }
3593
-
3594
- .group-member-name {
3595
- font-size: 12px;
3596
- color: #374151;
3597
- overflow: hidden;
3598
- text-overflow: ellipsis;
3599
- white-space: nowrap;
3600
- width: 100%;
3601
- }
3602
-
3603
- .invite-member-content {
3604
- display: flex;
3605
- flex-direction: column;
3606
- gap: 16px;
3607
- }
1291
+ <style scoped lang="scss">
1292
+ @use '../styles/components/chat-panel';
3608
1293
  </style>