vue-chat-kit 0.3.7 → 0.3.9
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.
- package/README.md +0 -4
- package/dist/vue-chat-kit.css +1 -1
- package/dist/vue-chat-kit.es.js +3392 -2122
- package/dist/vue-chat-kit.umd.js +1 -1
- package/package.json +1 -1
- package/src/components/ChatPanel.vue +1570 -57
- package/src/components/EmojiPicker.vue +197 -0
- package/src/composables/useChat.js +157 -613
- package/src/composables/useChatCore.js +207 -0
- package/src/composables/useFriendChat.js +423 -0
- package/src/composables/useGroupChat.js +748 -0
- package/src/config/index.js +21 -4
- package/src/core/adapter-example.js +90 -20
- package/src/core/api.js +189 -13
- package/src/core/websocket.js +25 -9
- package/src/index.js +0 -4
- package/src/components/ChatWindow.vue +0 -2094
|
@@ -159,6 +159,70 @@
|
|
|
159
159
|
>同意</el-button>
|
|
160
160
|
</div>
|
|
161
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>
|
|
162
226
|
</div>
|
|
163
227
|
</div>
|
|
164
228
|
|
|
@@ -192,13 +256,14 @@
|
|
|
192
256
|
</el-button>
|
|
193
257
|
</div>
|
|
194
258
|
|
|
195
|
-
<!--
|
|
196
|
-
<div v-if="currentChat" class="chat-window-area">
|
|
259
|
+
<!-- 聊天窗口(群聊或单聊) -->
|
|
260
|
+
<div v-if="currentChat || currentSelectGroup" class="chat-window-area">
|
|
197
261
|
<!-- 顶部标题栏 -->
|
|
198
262
|
<div class="chat-window-header">
|
|
199
263
|
<div class="chat-window-title">
|
|
200
|
-
<span class="chat-window-name">{{ currentChat.name }}</span>
|
|
264
|
+
<span class="chat-window-name">{{ currentSelectGroup ? currentSelectGroup.name : currentChat.name }}</span>
|
|
201
265
|
<span
|
|
266
|
+
v-if="!currentSelectGroup"
|
|
202
267
|
:class="[
|
|
203
268
|
'chat-window-status',
|
|
204
269
|
currentChat.online ? 'chat-window-status-online' : 'chat-window-status-offline'
|
|
@@ -206,10 +271,19 @@
|
|
|
206
271
|
>
|
|
207
272
|
{{ currentChat.online ? '在线' : '离线' }}
|
|
208
273
|
</span>
|
|
274
|
+
<span v-if="currentSelectGroup" class="chat-window-status">
|
|
275
|
+
{{ groupMemberList.length }} 人
|
|
276
|
+
</span>
|
|
209
277
|
</div>
|
|
210
278
|
<div class="chat-window-actions">
|
|
211
279
|
<el-icon class="chat-action-icon"><Search /></el-icon>
|
|
212
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
|
|
213
287
|
class="chat-action-icon"
|
|
214
288
|
@click="showChatDetail = !showChatDetail"
|
|
215
289
|
><MoreFilled /></el-icon>
|
|
@@ -222,7 +296,7 @@
|
|
|
222
296
|
class="chat-messages-container"
|
|
223
297
|
>
|
|
224
298
|
<div
|
|
225
|
-
v-for="(msg, index) in currentMessages"
|
|
299
|
+
v-for="(msg, index) in currentSelectGroup ? currentGroupMessages : currentMessages"
|
|
226
300
|
:key="index"
|
|
227
301
|
:class="[
|
|
228
302
|
'message-wrapper',
|
|
@@ -232,7 +306,7 @@
|
|
|
232
306
|
<!-- 头像 -->
|
|
233
307
|
<div class="message-avatar">
|
|
234
308
|
<img
|
|
235
|
-
:src="msg.
|
|
309
|
+
:src="msg.avatar"
|
|
236
310
|
class="message-avatar-img"
|
|
237
311
|
/>
|
|
238
312
|
</div>
|
|
@@ -244,7 +318,9 @@
|
|
|
244
318
|
msg.isSelf ? 'message-content-self' : 'message-content-other'
|
|
245
319
|
]"
|
|
246
320
|
>
|
|
247
|
-
<div v-if="!msg.isSelf" class="message-sender-name">
|
|
321
|
+
<div v-if="!msg.isSelf" class="message-sender-name">
|
|
322
|
+
{{ currentSelectGroup ? (msg.displayName || msg.sendUsername) : currentChat.name }}
|
|
323
|
+
</div>
|
|
248
324
|
|
|
249
325
|
<div class="message-bubble-wrapper">
|
|
250
326
|
<!-- 文本消息 -->
|
|
@@ -317,7 +393,7 @@
|
|
|
317
393
|
</div>
|
|
318
394
|
|
|
319
395
|
<!-- 底部输入区域 -->
|
|
320
|
-
<div class="chat-input-area">
|
|
396
|
+
<div class="chat-input-area" @click="showEmojiPicker = false">
|
|
321
397
|
<!-- 待发送文件预览 -->
|
|
322
398
|
<div
|
|
323
399
|
v-if="pendingFiles.length > 0"
|
|
@@ -363,7 +439,16 @@
|
|
|
363
439
|
</div>
|
|
364
440
|
|
|
365
441
|
<div v-if="config.modules.fileUpload" class="input-toolbar">
|
|
366
|
-
<
|
|
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"
|
|
450
|
+
/>
|
|
451
|
+
</div>
|
|
367
452
|
<el-icon
|
|
368
453
|
class="input-toolbar-icon"
|
|
369
454
|
@click="triggerFileSelect"
|
|
@@ -414,6 +499,202 @@
|
|
|
414
499
|
</div>
|
|
415
500
|
</div>
|
|
416
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" />
|
|
676
|
+
</div>
|
|
677
|
+
|
|
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>
|
|
697
|
+
|
|
417
698
|
<!-- 右侧详情面板 -->
|
|
418
699
|
<div
|
|
419
700
|
v-if="showChatDetail"
|
|
@@ -476,6 +757,286 @@
|
|
|
476
757
|
</div>
|
|
477
758
|
</el-dialog>
|
|
478
759
|
|
|
760
|
+
<!-- 创建群聊弹窗 -->
|
|
761
|
+
<el-dialog
|
|
762
|
+
v-model="createGroupDialogVisible"
|
|
763
|
+
title="创建群聊"
|
|
764
|
+
width="600px"
|
|
765
|
+
append-to-body
|
|
766
|
+
>
|
|
767
|
+
<div class="create-group-form">
|
|
768
|
+
<div class="form-item">
|
|
769
|
+
<label class="form-label">群名称</label>
|
|
770
|
+
<el-input
|
|
771
|
+
v-model="newGroupName"
|
|
772
|
+
placeholder="请输入群名称"
|
|
773
|
+
maxlength="50"
|
|
774
|
+
/>
|
|
775
|
+
</div>
|
|
776
|
+
<div class="form-item">
|
|
777
|
+
<label class="form-label">群备注</label>
|
|
778
|
+
<el-input
|
|
779
|
+
v-model="newGroupRemark"
|
|
780
|
+
type="textarea"
|
|
781
|
+
placeholder="请输入群备注(可选)"
|
|
782
|
+
:rows="3"
|
|
783
|
+
maxlength="200"
|
|
784
|
+
/>
|
|
785
|
+
</div>
|
|
786
|
+
<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>
|
|
901
|
+
</div>
|
|
902
|
+
</div>
|
|
903
|
+
<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>
|
|
931
|
+
</template>
|
|
932
|
+
</el-dialog>
|
|
933
|
+
|
|
934
|
+
<!-- 编辑群成员昵称弹窗 -->
|
|
935
|
+
<el-dialog
|
|
936
|
+
v-if="currentSelectGroup"
|
|
937
|
+
v-model="memberNickDialogVisible"
|
|
938
|
+
title="编辑群昵称"
|
|
939
|
+
width="400px"
|
|
940
|
+
append-to-body
|
|
941
|
+
>
|
|
942
|
+
<el-form label-width="80px">
|
|
943
|
+
<el-form-item label="成员">
|
|
944
|
+
<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>
|
|
950
|
+
<template #footer>
|
|
951
|
+
<el-button @click="memberNickDialogVisible = false">取消</el-button>
|
|
952
|
+
<el-button type="primary" @click="handleUpdateMemberNick">保存</el-button>
|
|
953
|
+
</template>
|
|
954
|
+
</el-dialog>
|
|
955
|
+
|
|
956
|
+
<!-- 消息已读成员弹窗 -->
|
|
957
|
+
<el-dialog
|
|
958
|
+
v-if="currentSelectGroup"
|
|
959
|
+
v-model="msgReadUserDialogVisible"
|
|
960
|
+
title="消息已读状态"
|
|
961
|
+
width="500px"
|
|
962
|
+
append-to-body
|
|
963
|
+
>
|
|
964
|
+
<div class="msg-read-users-content">
|
|
965
|
+
<div class="read-users-section">
|
|
966
|
+
<div class="section-title">
|
|
967
|
+
<el-icon><Check /></el-icon>已读 ({{ currentMsgReadList.readUserList.length }})
|
|
968
|
+
</div>
|
|
969
|
+
<div class="users-list">
|
|
970
|
+
<el-empty v-if="currentMsgReadList.readUserList.length === 0" description="暂无已读成员" />
|
|
971
|
+
<div v-else class="users-tag-list">
|
|
972
|
+
<el-tag v-for="user in currentMsgReadList.readUserList" :key="user" class="user-tag">
|
|
973
|
+
{{ user }}
|
|
974
|
+
</el-tag>
|
|
975
|
+
</div>
|
|
976
|
+
</div>
|
|
977
|
+
</div>
|
|
978
|
+
<div class="unread-users-section">
|
|
979
|
+
<div class="section-title">
|
|
980
|
+
<el-icon><Clock /></el-icon>未读 ({{ currentMsgReadList.unreadUserList.length }})
|
|
981
|
+
</div>
|
|
982
|
+
<div class="users-list">
|
|
983
|
+
<el-empty v-if="currentMsgReadList.unreadUserList.length === 0" description="全部已读" />
|
|
984
|
+
<div v-else class="users-tag-list">
|
|
985
|
+
<el-tag v-for="user in currentMsgReadList.unreadUserList" :key="user" type="info" class="user-tag">
|
|
986
|
+
{{ user }}
|
|
987
|
+
</el-tag>
|
|
988
|
+
</div>
|
|
989
|
+
</div>
|
|
990
|
+
</div>
|
|
991
|
+
</div>
|
|
992
|
+
<template #footer>
|
|
993
|
+
<el-button @click="msgReadUserDialogVisible = false">关闭</el-button>
|
|
994
|
+
</template>
|
|
995
|
+
</el-dialog>
|
|
996
|
+
|
|
997
|
+
<!-- 邀请成员弹窗 -->
|
|
998
|
+
<el-dialog
|
|
999
|
+
v-if="currentSelectGroup"
|
|
1000
|
+
v-model="inviteMemberDialogVisible"
|
|
1001
|
+
title="邀请成员"
|
|
1002
|
+
width="500px"
|
|
1003
|
+
append-to-body
|
|
1004
|
+
>
|
|
1005
|
+
<div class="invite-member-content">
|
|
1006
|
+
<div class="invite-member-list">
|
|
1007
|
+
<el-empty v-if="filteredFriendList.length === 0" description="暂无好友可以邀请" />
|
|
1008
|
+
<div
|
|
1009
|
+
v-else
|
|
1010
|
+
class="member-list"
|
|
1011
|
+
>
|
|
1012
|
+
<div
|
|
1013
|
+
v-for="friend in filteredFriendList"
|
|
1014
|
+
:key="friend.id"
|
|
1015
|
+
:class="[
|
|
1016
|
+
'member-item',
|
|
1017
|
+
selectedMembersForInvite.some(m => m.id === friend.id) ? 'member-selected' : ''
|
|
1018
|
+
]"
|
|
1019
|
+
@click="toggleMemberForInvite(friend)"
|
|
1020
|
+
>
|
|
1021
|
+
<img
|
|
1022
|
+
:src="friend.avatar"
|
|
1023
|
+
:alt="friend.name"
|
|
1024
|
+
class="member-avatar"
|
|
1025
|
+
/>
|
|
1026
|
+
<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>
|
|
1028
|
+
</div>
|
|
1029
|
+
</div>
|
|
1030
|
+
</div>
|
|
1031
|
+
</div>
|
|
1032
|
+
<template #footer>
|
|
1033
|
+
<el-button @click="inviteMemberDialogVisible = false">取消</el-button>
|
|
1034
|
+
<el-button type="primary" @click="handleInviteMember" :disabled="selectedMembersForInvite.length === 0">
|
|
1035
|
+
邀请 {{ selectedMembersForInvite.length }} 人
|
|
1036
|
+
</el-button>
|
|
1037
|
+
</template>
|
|
1038
|
+
</el-dialog>
|
|
1039
|
+
|
|
479
1040
|
<!-- 用户设置弹窗 -->
|
|
480
1041
|
<el-dialog
|
|
481
1042
|
v-model="showSettingsDialog"
|
|
@@ -652,11 +1213,19 @@ import {
|
|
|
652
1213
|
Bell,
|
|
653
1214
|
Setting,
|
|
654
1215
|
Document,
|
|
655
|
-
Download
|
|
1216
|
+
Download,
|
|
1217
|
+
CircleCheck,
|
|
1218
|
+
Edit,
|
|
1219
|
+
Delete,
|
|
1220
|
+
Check,
|
|
1221
|
+
Clock,
|
|
1222
|
+
Close,
|
|
1223
|
+
ArrowRight
|
|
656
1224
|
} from '@element-plus/icons-vue'
|
|
657
1225
|
import { useChat } from '../composables/useChat.js'
|
|
658
|
-
import { ElMessage } from 'element-plus'
|
|
1226
|
+
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
659
1227
|
import AvatarCrop from './AvatarCrop.vue'
|
|
1228
|
+
import EmojiPicker from './EmojiPicker.vue'
|
|
660
1229
|
|
|
661
1230
|
const props = defineProps({
|
|
662
1231
|
config: { type: Object, required: true }
|
|
@@ -684,6 +1253,33 @@ const {
|
|
|
684
1253
|
loadingAvailableUsers,
|
|
685
1254
|
friendApplyList,
|
|
686
1255
|
loadingFriendApply,
|
|
1256
|
+
// ========== 群聊相关 ==========
|
|
1257
|
+
activeTab,
|
|
1258
|
+
groupList,
|
|
1259
|
+
currentSelectGroup,
|
|
1260
|
+
groupMsgList,
|
|
1261
|
+
groupMemberList,
|
|
1262
|
+
createGroupDialogVisible,
|
|
1263
|
+
groupDetailVisible,
|
|
1264
|
+
newGroupName,
|
|
1265
|
+
newGroupRemark,
|
|
1266
|
+
selectedMembersForCreate,
|
|
1267
|
+
inviteMemberDialogVisible,
|
|
1268
|
+
selectedMembersForInvite,
|
|
1269
|
+
filteredGroupList,
|
|
1270
|
+
currentChatTarget,
|
|
1271
|
+
currentGroupMessages,
|
|
1272
|
+
currentAllMessages,
|
|
1273
|
+
groupInfoVisible,
|
|
1274
|
+
editingGroupInfo,
|
|
1275
|
+
editingMemberNick,
|
|
1276
|
+
memberNickDialogVisible,
|
|
1277
|
+
msgReadUserDialogVisible,
|
|
1278
|
+
currentMsgReadList,
|
|
1279
|
+
currentGroupInfo,
|
|
1280
|
+
editingFields,
|
|
1281
|
+
tempEditValues,
|
|
1282
|
+
// ========== 方法 ==========
|
|
687
1283
|
formatTime,
|
|
688
1284
|
formatLastTime,
|
|
689
1285
|
scrollToBottom,
|
|
@@ -702,8 +1298,35 @@ const {
|
|
|
702
1298
|
loadFriendApplyList,
|
|
703
1299
|
agreeFriend,
|
|
704
1300
|
updateMyAvatar,
|
|
705
|
-
|
|
706
|
-
|
|
1301
|
+
// ========== 群聊相关方法 ==========
|
|
1302
|
+
getGroupList,
|
|
1303
|
+
getGroupHistory,
|
|
1304
|
+
getGroupMembers,
|
|
1305
|
+
selectGroup,
|
|
1306
|
+
createGroup,
|
|
1307
|
+
inviteGroupMember,
|
|
1308
|
+
quitGroup,
|
|
1309
|
+
sendGroupMessage,
|
|
1310
|
+
sendGroupFile,
|
|
1311
|
+
sendGroupFilesAndText,
|
|
1312
|
+
switchTab,
|
|
1313
|
+
getGroupUnreadCount,
|
|
1314
|
+
getMsgReadUserList,
|
|
1315
|
+
updateGroupInfo,
|
|
1316
|
+
updateMemberNick,
|
|
1317
|
+
deleteGroup,
|
|
1318
|
+
removeGroupMember,
|
|
1319
|
+
transferGroupOwner,
|
|
1320
|
+
readSingleGroupMsg,
|
|
1321
|
+
readAllGroupMsg,
|
|
1322
|
+
openEditGroupInfo,
|
|
1323
|
+
openEditMemberNick,
|
|
1324
|
+
fetchGroupDetail,
|
|
1325
|
+
updateSingleGroupField,
|
|
1326
|
+
startEditField,
|
|
1327
|
+
cancelEditField,
|
|
1328
|
+
saveEditField
|
|
1329
|
+
|
|
707
1330
|
} = useChat(props.config, (message) => {
|
|
708
1331
|
emit('message', message)
|
|
709
1332
|
})
|
|
@@ -715,6 +1338,10 @@ const navTabs = computed(() => {
|
|
|
715
1338
|
tabs.push({ id: 'friends', icon: UserFilled, badge: 0 })
|
|
716
1339
|
}
|
|
717
1340
|
|
|
1341
|
+
if (props.config.modules.groups) {
|
|
1342
|
+
tabs.push({ id: 'groups', icon: UserFilled, badge: 0 })
|
|
1343
|
+
}
|
|
1344
|
+
|
|
718
1345
|
if (props.config.modules.apply) {
|
|
719
1346
|
tabs.push({ id: 'apply', icon: Bell, badge: friendApplyList.value?.length || 0 })
|
|
720
1347
|
}
|
|
@@ -738,6 +1365,33 @@ const avatarImageSrc = ref('')
|
|
|
738
1365
|
const fileInputRef = ref(null)
|
|
739
1366
|
const pendingFiles = ref([])
|
|
740
1367
|
const contextMenu = ref({ visible: false, x: 0, y: 0, chat: null })
|
|
1368
|
+
const showEmojiPicker = ref(false)
|
|
1369
|
+
|
|
1370
|
+
// 群聊侧边栏相关
|
|
1371
|
+
const showGroupSidebar = ref(false)
|
|
1372
|
+
const muteGroup = ref(false)
|
|
1373
|
+
const groupNameInputRef = ref(null)
|
|
1374
|
+
|
|
1375
|
+
// 监听侧边栏打开,获取群详情
|
|
1376
|
+
watch(showGroupSidebar, (newVal) => {
|
|
1377
|
+
if (newVal && currentSelectGroup.value) {
|
|
1378
|
+
fetchGroupDetail(currentSelectGroup.value.groupId)
|
|
1379
|
+
}
|
|
1380
|
+
})
|
|
1381
|
+
|
|
1382
|
+
// 计算属性:获取我在群里的昵称
|
|
1383
|
+
const myNicknameInGroup = computed(() => {
|
|
1384
|
+
const me = groupMemberList.value.find(m => m.username === myUsername)
|
|
1385
|
+
return me?.memberNick || ''
|
|
1386
|
+
})
|
|
1387
|
+
|
|
1388
|
+
// 打开编辑我自己的群昵称
|
|
1389
|
+
const openMyNicknameEdit = () => {
|
|
1390
|
+
const me = groupMemberList.value.find(m => m.username === myUsername)
|
|
1391
|
+
if (me) {
|
|
1392
|
+
openEditMemberNick(me)
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
741
1395
|
|
|
742
1396
|
const showContextMenuFn = (e, chat) => {
|
|
743
1397
|
e.preventDefault()
|
|
@@ -765,7 +1419,9 @@ const selectChat = (chat) => {
|
|
|
765
1419
|
currentChatId.value = chat.id
|
|
766
1420
|
currentChat.value = chat
|
|
767
1421
|
currentSelectedFriend.value = null
|
|
1422
|
+
currentSelectGroup.value = null
|
|
768
1423
|
showChatDetail.value = false
|
|
1424
|
+
groupDetailVisible.value = false
|
|
769
1425
|
selectUser({
|
|
770
1426
|
id: chat.id,
|
|
771
1427
|
name: chat.name,
|
|
@@ -778,6 +1434,17 @@ const selectFriend = (friend) => {
|
|
|
778
1434
|
currentSelectedFriend.value = friend
|
|
779
1435
|
currentChatId.value = null
|
|
780
1436
|
currentChat.value = null
|
|
1437
|
+
currentSelectGroup.value = null
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
const selectGroupChat = (group) => {
|
|
1441
|
+
currentSelectGroup.value = group
|
|
1442
|
+
currentChatId.value = null
|
|
1443
|
+
currentChat.value = null
|
|
1444
|
+
currentSelectedFriend.value = null
|
|
1445
|
+
showChatDetail.value = false
|
|
1446
|
+
groupDetailVisible.value = false
|
|
1447
|
+
selectGroup(group)
|
|
781
1448
|
}
|
|
782
1449
|
|
|
783
1450
|
const handleStartChat = async () => {
|
|
@@ -864,7 +1531,15 @@ const handleSend = async () => {
|
|
|
864
1531
|
})
|
|
865
1532
|
pendingFiles.value = []
|
|
866
1533
|
|
|
867
|
-
|
|
1534
|
+
// 根据当前选择的是群聊还是单聊来发送
|
|
1535
|
+
if (currentSelectGroup.value) {
|
|
1536
|
+
// 群聊发送
|
|
1537
|
+
await sendGroupFilesAndText(filesToSend, textToSend)
|
|
1538
|
+
} else {
|
|
1539
|
+
// 单聊发送
|
|
1540
|
+
await sendFilesAndText(filesToSend, textToSend)
|
|
1541
|
+
}
|
|
1542
|
+
|
|
868
1543
|
emit('send', { text: textToSend, files: filesToSend })
|
|
869
1544
|
}
|
|
870
1545
|
|
|
@@ -908,34 +1583,271 @@ const handleImageError = (e) => {
|
|
|
908
1583
|
console.warn('图片加载失败', e)
|
|
909
1584
|
}
|
|
910
1585
|
|
|
911
|
-
const
|
|
912
|
-
|
|
913
|
-
|
|
1586
|
+
const selectEmoji = (emoji) => {
|
|
1587
|
+
inputText.value += emoji
|
|
1588
|
+
showEmojiPicker.value = false
|
|
1589
|
+
}
|
|
914
1590
|
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
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%'
|
|
918
1612
|
}
|
|
1613
|
+
|
|
1614
|
+
return styles
|
|
1615
|
+
}
|
|
919
1616
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1617
|
+
// ========== 群聊相关方法 ==========
|
|
1618
|
+
const toggleMemberForCreate = (friend) => {
|
|
1619
|
+
const index = selectedMembersForCreate.value.findIndex(m => m.id === friend.id)
|
|
1620
|
+
if (index !== -1) {
|
|
1621
|
+
selectedMembersForCreate.value.splice(index, 1)
|
|
1622
|
+
} else {
|
|
1623
|
+
selectedMembersForCreate.value.push(friend)
|
|
923
1624
|
}
|
|
1625
|
+
}
|
|
924
1626
|
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1627
|
+
const toggleMemberForInvite = (friend) => {
|
|
1628
|
+
const index = selectedMembersForInvite.value.findIndex(m => m.id === friend.id)
|
|
1629
|
+
if (index !== -1) {
|
|
1630
|
+
selectedMembersForInvite.value.splice(index, 1)
|
|
1631
|
+
} else {
|
|
1632
|
+
selectedMembersForInvite.value.push(friend)
|
|
929
1633
|
}
|
|
930
|
-
reader.readAsDataURL(file)
|
|
931
1634
|
}
|
|
932
1635
|
|
|
933
|
-
const
|
|
934
|
-
|
|
1636
|
+
const handleCreateGroup = async () => {
|
|
1637
|
+
const success = await createGroup()
|
|
1638
|
+
if (success) {
|
|
1639
|
+
ElMessage.success('群聊创建成功')
|
|
1640
|
+
} else {
|
|
1641
|
+
ElMessage.error('群聊创建失败')
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
935
1644
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
1645
|
+
const handleInviteMember = async () => {
|
|
1646
|
+
const success = await inviteGroupMember()
|
|
1647
|
+
if (success) {
|
|
1648
|
+
ElMessage.success('邀请成功')
|
|
1649
|
+
} else {
|
|
1650
|
+
ElMessage.error('邀请失败')
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
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
|
+
}
|
|
1670
|
+
|
|
1671
|
+
// 处理解散群聊
|
|
1672
|
+
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
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
// 处理保存编辑字段
|
|
1695
|
+
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
|
+
try {
|
|
1703
|
+
const success = await saveEditField(field)
|
|
1704
|
+
if (success) {
|
|
1705
|
+
ElMessage.success('保存成功')
|
|
1706
|
+
} else {
|
|
1707
|
+
ElMessage.error('保存失败')
|
|
1708
|
+
}
|
|
1709
|
+
} catch (error) {
|
|
1710
|
+
console.error('保存失败', error)
|
|
1711
|
+
ElMessage.error('保存失败')
|
|
1712
|
+
} finally {
|
|
1713
|
+
loading.close()
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
// 处理保存群成员昵称
|
|
1718
|
+
const handleUpdateMemberNick = async () => {
|
|
1719
|
+
const loading = ElLoading.service({
|
|
1720
|
+
lock: true,
|
|
1721
|
+
text: '保存中...',
|
|
1722
|
+
background: 'rgba(0, 0, 0, 0.7)',
|
|
1723
|
+
})
|
|
1724
|
+
|
|
1725
|
+
try {
|
|
1726
|
+
const success = await updateMemberNick()
|
|
1727
|
+
if (success) {
|
|
1728
|
+
ElMessage.success('群昵称修改成功')
|
|
1729
|
+
memberNickDialogVisible.value = false
|
|
1730
|
+
} else {
|
|
1731
|
+
ElMessage.error('修改失败')
|
|
1732
|
+
}
|
|
1733
|
+
} catch (error) {
|
|
1734
|
+
console.error('修改失败', error)
|
|
1735
|
+
ElMessage.error('修改失败')
|
|
1736
|
+
} finally {
|
|
1737
|
+
loading.close()
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
// 处理移除群成员
|
|
1742
|
+
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('移除失败')
|
|
1766
|
+
}
|
|
1767
|
+
} catch (error) {
|
|
1768
|
+
console.error('移除失败', error)
|
|
1769
|
+
ElMessage.error('移除失败')
|
|
1770
|
+
} finally {
|
|
1771
|
+
loading.close()
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
// 处理转让群主
|
|
1777
|
+
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
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
// 处理更新群信息
|
|
1812
|
+
const handleUpdateGroupInfo = async () => {
|
|
1813
|
+
const success = await updateGroupInfo()
|
|
1814
|
+
if (success) {
|
|
1815
|
+
ElMessage.success('群信息已更新')
|
|
1816
|
+
} else {
|
|
1817
|
+
ElMessage.error('更新失败')
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
|
|
1822
|
+
|
|
1823
|
+
const handleAvatarFileChange = (e) => {
|
|
1824
|
+
const file = e.target.files[0]
|
|
1825
|
+
if (!file) return
|
|
1826
|
+
|
|
1827
|
+
if (!file.type.startsWith('image/')) {
|
|
1828
|
+
ElMessage.error('只能上传图片文件')
|
|
1829
|
+
return
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
if (file.size > 5 * 1024 * 1024) {
|
|
1833
|
+
ElMessage.error('图片大小不能超过 5MB')
|
|
1834
|
+
return
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
const reader = new FileReader()
|
|
1838
|
+
reader.onload = (event) => {
|
|
1839
|
+
avatarImageSrc.value = event.target.result
|
|
1840
|
+
showAvatarEditor.value = true
|
|
1841
|
+
}
|
|
1842
|
+
reader.readAsDataURL(file)
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
const handleAvatarCropConfirm = async ({ file }) => {
|
|
1846
|
+
if (!file) return
|
|
1847
|
+
|
|
1848
|
+
avatarUploading.value = true
|
|
1849
|
+
try {
|
|
1850
|
+
const { ChatApi } = await import('../core/api.js')
|
|
939
1851
|
const api = new ChatApi(props.config)
|
|
940
1852
|
const res = await api.uploadAvatar(file, myUsername)
|
|
941
1853
|
if (res.code === 200) {
|
|
@@ -996,11 +1908,21 @@ const saveUserInfo = async () => {
|
|
|
996
1908
|
|
|
997
1909
|
// 生命周期
|
|
998
1910
|
onMounted(async () => {
|
|
999
|
-
|
|
1911
|
+
const promises = [getFriendList(), loadFriendApplyList()]
|
|
1912
|
+
|
|
1913
|
+
// 如果启用了群聊模块,也加载群聊列表
|
|
1914
|
+
if (props.config.modules.groups) {
|
|
1915
|
+
promises.push(getGroupList())
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
await Promise.all(promises)
|
|
1919
|
+
|
|
1000
1920
|
initWebSocket()
|
|
1921
|
+
|
|
1001
1922
|
if (filteredUsers.value.length > 0) {
|
|
1002
1923
|
selectChat(filteredUsers.value[0])
|
|
1003
1924
|
}
|
|
1925
|
+
|
|
1004
1926
|
emit('init')
|
|
1005
1927
|
document.addEventListener('click', hideContextMenu)
|
|
1006
1928
|
})
|
|
@@ -1017,10 +1939,14 @@ onUnmounted(() => {
|
|
|
1017
1939
|
.chat-panel {
|
|
1018
1940
|
display: flex;
|
|
1019
1941
|
height: 680px;
|
|
1020
|
-
background
|
|
1942
|
+
background: rgba(255, 255, 255, 0.7);
|
|
1943
|
+
backdrop-filter: blur(20px);
|
|
1944
|
+
-webkit-backdrop-filter: blur(20px);
|
|
1021
1945
|
overflow: hidden;
|
|
1022
|
-
border-radius:
|
|
1023
|
-
box-shadow: 0
|
|
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);
|
|
1024
1950
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
1025
1951
|
}
|
|
1026
1952
|
|
|
@@ -1031,8 +1957,10 @@ onUnmounted(() => {
|
|
|
1031
1957
|
flex-direction: column;
|
|
1032
1958
|
align-items: center;
|
|
1033
1959
|
gap: 8px;
|
|
1034
|
-
background
|
|
1035
|
-
|
|
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);
|
|
1036
1964
|
}
|
|
1037
1965
|
|
|
1038
1966
|
.sidebar-avatar {
|
|
@@ -1096,8 +2024,10 @@ onUnmounted(() => {
|
|
|
1096
2024
|
/* ========== 中间内容面板 ========== */
|
|
1097
2025
|
.chat-content-panel {
|
|
1098
2026
|
width: 288px;
|
|
1099
|
-
background
|
|
1100
|
-
|
|
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);
|
|
1101
2031
|
display: flex;
|
|
1102
2032
|
flex-direction: column;
|
|
1103
2033
|
}
|
|
@@ -1123,14 +2053,16 @@ onUnmounted(() => {
|
|
|
1123
2053
|
padding: 12px;
|
|
1124
2054
|
cursor: pointer;
|
|
1125
2055
|
transition: background-color 0.2s;
|
|
2056
|
+
border-radius: 8px;
|
|
2057
|
+
margin: 0 8px;
|
|
1126
2058
|
}
|
|
1127
2059
|
|
|
1128
2060
|
.chat-list-item:hover {
|
|
1129
|
-
background
|
|
2061
|
+
background: rgba(229, 229, 229, 0.6);
|
|
1130
2062
|
}
|
|
1131
2063
|
|
|
1132
2064
|
.chat-list-item-active {
|
|
1133
|
-
background
|
|
2065
|
+
background: rgba(7, 193, 96, 0.15);
|
|
1134
2066
|
}
|
|
1135
2067
|
|
|
1136
2068
|
.chat-list-avatar-wrapper {
|
|
@@ -1145,6 +2077,29 @@ onUnmounted(() => {
|
|
|
1145
2077
|
object-fit: cover;
|
|
1146
2078
|
}
|
|
1147
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
|
+
|
|
1148
2103
|
.chat-list-online-indicator {
|
|
1149
2104
|
position: absolute;
|
|
1150
2105
|
bottom: 0;
|
|
@@ -1295,7 +2250,9 @@ onUnmounted(() => {
|
|
|
1295
2250
|
display: flex;
|
|
1296
2251
|
flex-direction: column;
|
|
1297
2252
|
min-width: 0;
|
|
1298
|
-
background
|
|
2253
|
+
background: rgba(255, 255, 255, 0.5);
|
|
2254
|
+
backdrop-filter: blur(10px);
|
|
2255
|
+
-webkit-backdrop-filter: blur(10px);
|
|
1299
2256
|
}
|
|
1300
2257
|
|
|
1301
2258
|
/* 好友信息 */
|
|
@@ -1306,7 +2263,7 @@ onUnmounted(() => {
|
|
|
1306
2263
|
align-items: center;
|
|
1307
2264
|
justify-content: center;
|
|
1308
2265
|
padding: 32px;
|
|
1309
|
-
background
|
|
2266
|
+
background: rgba(245, 245, 245, 0.4);
|
|
1310
2267
|
}
|
|
1311
2268
|
|
|
1312
2269
|
.profile-avatar {
|
|
@@ -1359,12 +2316,14 @@ onUnmounted(() => {
|
|
|
1359
2316
|
|
|
1360
2317
|
.chat-window-header {
|
|
1361
2318
|
height: 56px;
|
|
1362
|
-
border-bottom: 1px solid
|
|
2319
|
+
border-bottom: 1px solid rgba(229, 231, 235, 0.5);
|
|
1363
2320
|
display: flex;
|
|
1364
2321
|
align-items: center;
|
|
1365
2322
|
justify-content: space-between;
|
|
1366
2323
|
padding: 0 16px;
|
|
1367
|
-
background
|
|
2324
|
+
background: rgba(255, 255, 255, 0.3);
|
|
2325
|
+
backdrop-filter: blur(10px);
|
|
2326
|
+
-webkit-backdrop-filter: blur(10px);
|
|
1368
2327
|
}
|
|
1369
2328
|
|
|
1370
2329
|
.chat-window-title {
|
|
@@ -1414,7 +2373,7 @@ onUnmounted(() => {
|
|
|
1414
2373
|
flex: 1;
|
|
1415
2374
|
overflow-y: auto;
|
|
1416
2375
|
padding: 16px;
|
|
1417
|
-
background
|
|
2376
|
+
background: rgba(245, 245, 245, 0.3);
|
|
1418
2377
|
min-height: 0;
|
|
1419
2378
|
}
|
|
1420
2379
|
|
|
@@ -1641,8 +2600,17 @@ onUnmounted(() => {
|
|
|
1641
2600
|
|
|
1642
2601
|
/* 输入区域 */
|
|
1643
2602
|
.chat-input-area {
|
|
1644
|
-
background
|
|
1645
|
-
|
|
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;
|
|
1646
2614
|
}
|
|
1647
2615
|
|
|
1648
2616
|
.pending-files-area {
|
|
@@ -1800,8 +2768,10 @@ onUnmounted(() => {
|
|
|
1800
2768
|
/* ========== 右侧详情面板 ========== */
|
|
1801
2769
|
.chat-detail-panel {
|
|
1802
2770
|
width: 256px;
|
|
1803
|
-
background
|
|
1804
|
-
|
|
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);
|
|
1805
2775
|
display: flex;
|
|
1806
2776
|
flex-direction: column;
|
|
1807
2777
|
}
|
|
@@ -1811,7 +2781,7 @@ onUnmounted(() => {
|
|
|
1811
2781
|
display: flex;
|
|
1812
2782
|
align-items: center;
|
|
1813
2783
|
justify-content: center;
|
|
1814
|
-
border-bottom: 1px solid
|
|
2784
|
+
border-bottom: 1px solid rgba(229, 231, 235, 0.5);
|
|
1815
2785
|
font-weight: 500;
|
|
1816
2786
|
color: #374151;
|
|
1817
2787
|
font-size: 14px;
|
|
@@ -2074,10 +3044,12 @@ onUnmounted(() => {
|
|
|
2074
3044
|
/* ========== 右键菜单 ========== */
|
|
2075
3045
|
.chat-context-menu {
|
|
2076
3046
|
position: fixed;
|
|
2077
|
-
background
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
border:
|
|
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);
|
|
2081
3053
|
padding: 4px 0;
|
|
2082
3054
|
z-index: 1000;
|
|
2083
3055
|
}
|
|
@@ -2087,9 +3059,550 @@ onUnmounted(() => {
|
|
|
2087
3059
|
cursor: pointer;
|
|
2088
3060
|
font-size: 14px;
|
|
2089
3061
|
color: #374151;
|
|
3062
|
+
margin: 2px 4px;
|
|
3063
|
+
border-radius: 8px;
|
|
3064
|
+
transition: background-color 0.2s;
|
|
2090
3065
|
}
|
|
2091
3066
|
|
|
2092
3067
|
.chat-context-menu-item:hover {
|
|
2093
|
-
background-color:
|
|
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;
|
|
2094
3607
|
}
|
|
2095
3608
|
</style>
|