vue-chat-kit 0.3.8 → 0.3.10
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/dist/vue-chat-kit.css +1 -1
- package/dist/vue-chat-kit.es.js +2948 -2148
- package/dist/vue-chat-kit.umd.js +1 -1
- package/package.json +1 -1
- package/src/components/ChatPanel.vue +1381 -44
- package/src/composables/useChat.js +157 -596
- 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 -2
- package/src/core/adapter-example.js +90 -0
- package/src/core/api.js +189 -0
- package/src/core/websocket.js +25 -9
- package/src/index.js +0 -4
- package/src/components/ChatWindow.vue +0 -2144
|
@@ -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
|
<!-- 文本消息 -->
|
|
@@ -423,6 +499,202 @@
|
|
|
423
499
|
</div>
|
|
424
500
|
</div>
|
|
425
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
|
+
|
|
426
698
|
<!-- 右侧详情面板 -->
|
|
427
699
|
<div
|
|
428
700
|
v-if="showChatDetail"
|
|
@@ -485,6 +757,286 @@
|
|
|
485
757
|
</div>
|
|
486
758
|
</el-dialog>
|
|
487
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
|
+
|
|
488
1040
|
<!-- 用户设置弹窗 -->
|
|
489
1041
|
<el-dialog
|
|
490
1042
|
v-model="showSettingsDialog"
|
|
@@ -661,10 +1213,17 @@ import {
|
|
|
661
1213
|
Bell,
|
|
662
1214
|
Setting,
|
|
663
1215
|
Document,
|
|
664
|
-
Download
|
|
1216
|
+
Download,
|
|
1217
|
+
CircleCheck,
|
|
1218
|
+
Edit,
|
|
1219
|
+
Delete,
|
|
1220
|
+
Check,
|
|
1221
|
+
Clock,
|
|
1222
|
+
Close,
|
|
1223
|
+
ArrowRight
|
|
665
1224
|
} from '@element-plus/icons-vue'
|
|
666
1225
|
import { useChat } from '../composables/useChat.js'
|
|
667
|
-
import { ElMessage } from 'element-plus'
|
|
1226
|
+
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
668
1227
|
import AvatarCrop from './AvatarCrop.vue'
|
|
669
1228
|
import EmojiPicker from './EmojiPicker.vue'
|
|
670
1229
|
|
|
@@ -694,6 +1253,33 @@ const {
|
|
|
694
1253
|
loadingAvailableUsers,
|
|
695
1254
|
friendApplyList,
|
|
696
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
|
+
// ========== 方法 ==========
|
|
697
1283
|
formatTime,
|
|
698
1284
|
formatLastTime,
|
|
699
1285
|
scrollToBottom,
|
|
@@ -712,6 +1298,34 @@ const {
|
|
|
712
1298
|
loadFriendApplyList,
|
|
713
1299
|
agreeFriend,
|
|
714
1300
|
updateMyAvatar,
|
|
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
|
|
715
1329
|
|
|
716
1330
|
} = useChat(props.config, (message) => {
|
|
717
1331
|
emit('message', message)
|
|
@@ -724,6 +1338,10 @@ const navTabs = computed(() => {
|
|
|
724
1338
|
tabs.push({ id: 'friends', icon: UserFilled, badge: 0 })
|
|
725
1339
|
}
|
|
726
1340
|
|
|
1341
|
+
if (props.config.modules.groups) {
|
|
1342
|
+
tabs.push({ id: 'groups', icon: UserFilled, badge: 0 })
|
|
1343
|
+
}
|
|
1344
|
+
|
|
727
1345
|
if (props.config.modules.apply) {
|
|
728
1346
|
tabs.push({ id: 'apply', icon: Bell, badge: friendApplyList.value?.length || 0 })
|
|
729
1347
|
}
|
|
@@ -749,6 +1367,32 @@ const pendingFiles = ref([])
|
|
|
749
1367
|
const contextMenu = ref({ visible: false, x: 0, y: 0, chat: null })
|
|
750
1368
|
const showEmojiPicker = ref(false)
|
|
751
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
|
+
}
|
|
1395
|
+
|
|
752
1396
|
const showContextMenuFn = (e, chat) => {
|
|
753
1397
|
e.preventDefault()
|
|
754
1398
|
e.stopPropagation()
|
|
@@ -775,7 +1419,9 @@ const selectChat = (chat) => {
|
|
|
775
1419
|
currentChatId.value = chat.id
|
|
776
1420
|
currentChat.value = chat
|
|
777
1421
|
currentSelectedFriend.value = null
|
|
1422
|
+
currentSelectGroup.value = null
|
|
778
1423
|
showChatDetail.value = false
|
|
1424
|
+
groupDetailVisible.value = false
|
|
779
1425
|
selectUser({
|
|
780
1426
|
id: chat.id,
|
|
781
1427
|
name: chat.name,
|
|
@@ -788,6 +1434,17 @@ const selectFriend = (friend) => {
|
|
|
788
1434
|
currentSelectedFriend.value = friend
|
|
789
1435
|
currentChatId.value = null
|
|
790
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)
|
|
791
1448
|
}
|
|
792
1449
|
|
|
793
1450
|
const handleStartChat = async () => {
|
|
@@ -874,7 +1531,15 @@ const handleSend = async () => {
|
|
|
874
1531
|
})
|
|
875
1532
|
pendingFiles.value = []
|
|
876
1533
|
|
|
877
|
-
|
|
1534
|
+
// 根据当前选择的是群聊还是单聊来发送
|
|
1535
|
+
if (currentSelectGroup.value) {
|
|
1536
|
+
// 群聊发送
|
|
1537
|
+
await sendGroupFilesAndText(filesToSend, textToSend)
|
|
1538
|
+
} else {
|
|
1539
|
+
// 单聊发送
|
|
1540
|
+
await sendFilesAndText(filesToSend, textToSend)
|
|
1541
|
+
}
|
|
1542
|
+
|
|
878
1543
|
emit('send', { text: textToSend, files: filesToSend })
|
|
879
1544
|
}
|
|
880
1545
|
|
|
@@ -923,46 +1588,151 @@ const selectEmoji = (emoji) => {
|
|
|
923
1588
|
showEmojiPicker.value = false
|
|
924
1589
|
}
|
|
925
1590
|
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
if (
|
|
931
|
-
|
|
932
|
-
|
|
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%'
|
|
933
1612
|
}
|
|
1613
|
+
|
|
1614
|
+
return styles
|
|
1615
|
+
}
|
|
934
1616
|
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
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)
|
|
938
1624
|
}
|
|
1625
|
+
}
|
|
939
1626
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
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)
|
|
944
1633
|
}
|
|
945
|
-
reader.readAsDataURL(file)
|
|
946
1634
|
}
|
|
947
1635
|
|
|
948
|
-
const
|
|
949
|
-
|
|
1636
|
+
const handleCreateGroup = async () => {
|
|
1637
|
+
await createGroup()
|
|
1638
|
+
}
|
|
950
1639
|
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1640
|
+
const handleInviteMember = async () => {
|
|
1641
|
+
await inviteGroupMember()
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
const handleQuitGroup = async () => {
|
|
1645
|
+
await quitGroup()
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
// 处理解散群聊
|
|
1649
|
+
const handleDeleteGroup = async () => {
|
|
1650
|
+
await deleteGroup()
|
|
1651
|
+
showGroupSidebar.value = false
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
// 处理保存编辑字段
|
|
1655
|
+
const handleSaveEditField = async (field) => {
|
|
1656
|
+
try {
|
|
1657
|
+
await saveEditField(field)
|
|
1658
|
+
} catch (error) {
|
|
1659
|
+
console.error('保存失败', error)
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
// 处理保存群成员昵称
|
|
1664
|
+
const handleUpdateMemberNick = async () => {
|
|
1665
|
+
try {
|
|
1666
|
+
const success = await updateMemberNick()
|
|
1667
|
+
if (success) {
|
|
1668
|
+
memberNickDialogVisible.value = false
|
|
1669
|
+
}
|
|
1670
|
+
} catch (error) {
|
|
1671
|
+
console.error('修改失败', error)
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
// 处理移除群成员
|
|
1676
|
+
const handleRemoveMember = async (username) => {
|
|
1677
|
+
try {
|
|
1678
|
+
await removeGroupMember(username)
|
|
1679
|
+
} catch (error) {
|
|
1680
|
+
console.error('移除失败', error)
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
// 处理转让群主
|
|
1685
|
+
const handleTransferOwner = async (newOwnerUsername) => {
|
|
1686
|
+
try {
|
|
1687
|
+
await transferGroupOwner(newOwnerUsername)
|
|
1688
|
+
} catch (error) {
|
|
1689
|
+
console.error('转让失败', error)
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
// 处理更新群信息
|
|
1694
|
+
const handleUpdateGroupInfo = async () => {
|
|
1695
|
+
await updateGroupInfo()
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
|
|
1699
|
+
|
|
1700
|
+
const handleAvatarFileChange = (e) => {
|
|
1701
|
+
const file = e.target.files[0]
|
|
1702
|
+
if (!file) return
|
|
1703
|
+
|
|
1704
|
+
if (!file.type.startsWith('image/')) {
|
|
1705
|
+
console.error('只能上传图片文件')
|
|
1706
|
+
return
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
if (file.size > 5 * 1024 * 1024) {
|
|
1710
|
+
console.error('图片大小不能超过 5MB')
|
|
1711
|
+
return
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
const reader = new FileReader()
|
|
1715
|
+
reader.onload = (event) => {
|
|
1716
|
+
avatarImageSrc.value = event.target.result
|
|
1717
|
+
showAvatarEditor.value = true
|
|
1718
|
+
}
|
|
1719
|
+
reader.readAsDataURL(file)
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
const handleAvatarCropConfirm = async ({ file }) => {
|
|
1723
|
+
if (!file) return
|
|
1724
|
+
|
|
1725
|
+
avatarUploading.value = true
|
|
1726
|
+
try {
|
|
1727
|
+
const { ChatApi } = await import('../core/api.js')
|
|
1728
|
+
const api = new ChatApi(props.config)
|
|
1729
|
+
const res = await api.uploadAvatar(file, myUsername)
|
|
1730
|
+
if (res.code === 200) {
|
|
1731
|
+
updateMyAvatar(res.data)
|
|
1732
|
+
resetAvatar()
|
|
1733
|
+
}
|
|
963
1734
|
} catch (error) {
|
|
964
1735
|
console.error(error)
|
|
965
|
-
ElMessage.error('头像上传失败')
|
|
966
1736
|
} finally {
|
|
967
1737
|
avatarUploading.value = false
|
|
968
1738
|
}
|
|
@@ -996,14 +1766,10 @@ const saveUserInfo = async () => {
|
|
|
996
1766
|
try {
|
|
997
1767
|
const success = await updateUserInfo(editingUserInfo.value)
|
|
998
1768
|
if (success) {
|
|
999
|
-
ElMessage.success('保存成功')
|
|
1000
1769
|
isEditingUserInfo.value = false
|
|
1001
|
-
} else {
|
|
1002
|
-
ElMessage.error('保存失败')
|
|
1003
1770
|
}
|
|
1004
1771
|
} catch (error) {
|
|
1005
1772
|
console.error(error)
|
|
1006
|
-
ElMessage.error('保存失败')
|
|
1007
1773
|
} finally {
|
|
1008
1774
|
savingUserInfo.value = false
|
|
1009
1775
|
}
|
|
@@ -1011,11 +1777,21 @@ const saveUserInfo = async () => {
|
|
|
1011
1777
|
|
|
1012
1778
|
// 生命周期
|
|
1013
1779
|
onMounted(async () => {
|
|
1014
|
-
|
|
1780
|
+
const promises = [getFriendList(), loadFriendApplyList()]
|
|
1781
|
+
|
|
1782
|
+
// 如果启用了群聊模块,也加载群聊列表
|
|
1783
|
+
if (props.config.modules.groups) {
|
|
1784
|
+
promises.push(getGroupList())
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
await Promise.all(promises)
|
|
1788
|
+
|
|
1015
1789
|
initWebSocket()
|
|
1790
|
+
|
|
1016
1791
|
if (filteredUsers.value.length > 0) {
|
|
1017
1792
|
selectChat(filteredUsers.value[0])
|
|
1018
1793
|
}
|
|
1794
|
+
|
|
1019
1795
|
emit('init')
|
|
1020
1796
|
document.addEventListener('click', hideContextMenu)
|
|
1021
1797
|
})
|
|
@@ -1170,6 +1946,29 @@ onUnmounted(() => {
|
|
|
1170
1946
|
object-fit: cover;
|
|
1171
1947
|
}
|
|
1172
1948
|
|
|
1949
|
+
/* 多方格群聊头像 */
|
|
1950
|
+
.group-avatar-grid {
|
|
1951
|
+
width: 44px;
|
|
1952
|
+
height: 44px;
|
|
1953
|
+
border-radius: 8px;
|
|
1954
|
+
overflow: hidden;
|
|
1955
|
+
display: flex;
|
|
1956
|
+
flex-wrap: wrap;
|
|
1957
|
+
background-color: #f3f4f6;
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
.group-avatar-item {
|
|
1961
|
+
overflow: hidden;
|
|
1962
|
+
box-sizing: border-box;
|
|
1963
|
+
border: 0.5px solid rgba(255, 255, 255, 0.3);
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
.group-avatar-img {
|
|
1967
|
+
width: 100%;
|
|
1968
|
+
height: 100%;
|
|
1969
|
+
object-fit: cover;
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1173
1972
|
.chat-list-online-indicator {
|
|
1174
1973
|
position: absolute;
|
|
1175
1974
|
bottom: 0;
|
|
@@ -2137,4 +2936,542 @@ onUnmounted(() => {
|
|
|
2137
2936
|
.chat-context-menu-item:hover {
|
|
2138
2937
|
background-color: rgba(243, 244, 246, 0.8);
|
|
2139
2938
|
}
|
|
2939
|
+
|
|
2940
|
+
/* ========== 群聊相关样式 ========== */
|
|
2941
|
+
.create-group-form {
|
|
2942
|
+
display: flex;
|
|
2943
|
+
flex-direction: column;
|
|
2944
|
+
gap: 20px;
|
|
2945
|
+
}
|
|
2946
|
+
|
|
2947
|
+
.form-item {
|
|
2948
|
+
display: flex;
|
|
2949
|
+
flex-direction: column;
|
|
2950
|
+
gap: 8px;
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2953
|
+
.form-label {
|
|
2954
|
+
font-size: 14px;
|
|
2955
|
+
font-weight: 500;
|
|
2956
|
+
color: #374151;
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2959
|
+
.member-selection {
|
|
2960
|
+
border: 1px solid #e5e7eb;
|
|
2961
|
+
border-radius: 8px;
|
|
2962
|
+
padding: 12px;
|
|
2963
|
+
background-color: #f9fafb;
|
|
2964
|
+
}
|
|
2965
|
+
|
|
2966
|
+
.member-list {
|
|
2967
|
+
display: grid;
|
|
2968
|
+
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
|
2969
|
+
gap: 12px;
|
|
2970
|
+
}
|
|
2971
|
+
|
|
2972
|
+
.member-item {
|
|
2973
|
+
display: flex;
|
|
2974
|
+
flex-direction: column;
|
|
2975
|
+
align-items: center;
|
|
2976
|
+
padding: 12px 8px;
|
|
2977
|
+
border-radius: 8px;
|
|
2978
|
+
cursor: pointer;
|
|
2979
|
+
transition: background-color 0.2s;
|
|
2980
|
+
border: 2px solid transparent;
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
.member-item:hover {
|
|
2984
|
+
background-color: rgba(243, 244, 246, 0.8);
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2987
|
+
.member-item.member-selected {
|
|
2988
|
+
border-color: #07c160;
|
|
2989
|
+
background-color: rgba(7, 193, 96, 0.05);
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2992
|
+
.member-item img {
|
|
2993
|
+
width: 56px;
|
|
2994
|
+
height: 56px;
|
|
2995
|
+
border-radius: 50%;
|
|
2996
|
+
object-fit: cover;
|
|
2997
|
+
margin-bottom: 8px;
|
|
2998
|
+
}
|
|
2999
|
+
|
|
3000
|
+
.member-item span {
|
|
3001
|
+
font-size: 13px;
|
|
3002
|
+
color: #374151;
|
|
3003
|
+
text-align: center;
|
|
3004
|
+
overflow: hidden;
|
|
3005
|
+
text-overflow: ellipsis;
|
|
3006
|
+
white-space: nowrap;
|
|
3007
|
+
max-width: 100%;
|
|
3008
|
+
}
|
|
3009
|
+
|
|
3010
|
+
.member-check {
|
|
3011
|
+
color: #07c160;
|
|
3012
|
+
margin-top: 4px;
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
.group-detail-content {
|
|
3016
|
+
display: flex;
|
|
3017
|
+
flex-direction: column;
|
|
3018
|
+
gap: 16px;
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
.group-info-section {
|
|
3022
|
+
display: flex;
|
|
3023
|
+
flex-direction: column;
|
|
3024
|
+
gap: 12px;
|
|
3025
|
+
}
|
|
3026
|
+
|
|
3027
|
+
.group-info-item {
|
|
3028
|
+
display: flex;
|
|
3029
|
+
justify-content: space-between;
|
|
3030
|
+
align-items: center;
|
|
3031
|
+
padding: 8px 0;
|
|
3032
|
+
}
|
|
3033
|
+
|
|
3034
|
+
.info-label {
|
|
3035
|
+
font-size: 14px;
|
|
3036
|
+
color: #6b7280;
|
|
3037
|
+
}
|
|
3038
|
+
|
|
3039
|
+
.info-value {
|
|
3040
|
+
font-size: 14px;
|
|
3041
|
+
color: #1f2937;
|
|
3042
|
+
font-weight: 500;
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3045
|
+
.group-members-section {
|
|
3046
|
+
display: flex;
|
|
3047
|
+
flex-direction: column;
|
|
3048
|
+
gap: 12px;
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3051
|
+
.section-header {
|
|
3052
|
+
display: flex;
|
|
3053
|
+
justify-content: space-between;
|
|
3054
|
+
align-items: center;
|
|
3055
|
+
}
|
|
3056
|
+
|
|
3057
|
+
.section-title {
|
|
3058
|
+
font-size: 14px;
|
|
3059
|
+
font-weight: 500;
|
|
3060
|
+
color: #374151;
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
.group-members-list {
|
|
3064
|
+
display: flex;
|
|
3065
|
+
flex-direction: column;
|
|
3066
|
+
gap: 8px;
|
|
3067
|
+
max-height: 300px;
|
|
3068
|
+
overflow-y: auto;
|
|
3069
|
+
}
|
|
3070
|
+
|
|
3071
|
+
.group-member-item {
|
|
3072
|
+
display: flex;
|
|
3073
|
+
align-items: center;
|
|
3074
|
+
gap: 12px;
|
|
3075
|
+
padding: 8px 12px;
|
|
3076
|
+
border-radius: 8px;
|
|
3077
|
+
background-color: white;
|
|
3078
|
+
transition: background-color 0.2s;
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
.group-member-item:hover {
|
|
3082
|
+
background-color: #f9fafb;
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
.group-member-avatar {
|
|
3086
|
+
width: 40px;
|
|
3087
|
+
height: 40px;
|
|
3088
|
+
border-radius: 50%;
|
|
3089
|
+
object-fit: cover;
|
|
3090
|
+
flex-shrink: 0;
|
|
3091
|
+
}
|
|
3092
|
+
|
|
3093
|
+
.group-member-name {
|
|
3094
|
+
font-size: 14px;
|
|
3095
|
+
color: #1f2937;
|
|
3096
|
+
flex: 1;
|
|
3097
|
+
overflow: hidden;
|
|
3098
|
+
text-overflow: ellipsis;
|
|
3099
|
+
white-space: nowrap;
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
/* ========== 群聊侧边栏样式 ========== */
|
|
3103
|
+
.group-sidebar {
|
|
3104
|
+
width: 320px;
|
|
3105
|
+
background-color: #f5f5f5;
|
|
3106
|
+
border-left: 1px solid #e5e7eb;
|
|
3107
|
+
display: flex;
|
|
3108
|
+
flex-direction: column;
|
|
3109
|
+
height: 100%;
|
|
3110
|
+
overflow: hidden;
|
|
3111
|
+
}
|
|
3112
|
+
|
|
3113
|
+
.slide-enter-active,
|
|
3114
|
+
.slide-leave-active {
|
|
3115
|
+
transition: transform 0.3s ease;
|
|
3116
|
+
}
|
|
3117
|
+
|
|
3118
|
+
.slide-enter-from,
|
|
3119
|
+
.slide-leave-to {
|
|
3120
|
+
transform: translateX(100%);
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3123
|
+
.group-sidebar-header {
|
|
3124
|
+
height: 56px;
|
|
3125
|
+
display: flex;
|
|
3126
|
+
justify-content: space-between;
|
|
3127
|
+
align-items: center;
|
|
3128
|
+
padding: 0 16px;
|
|
3129
|
+
border-bottom: 1px solid #e5e7eb;
|
|
3130
|
+
background-color: white;
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
.group-sidebar-title {
|
|
3134
|
+
font-size: 16px;
|
|
3135
|
+
font-weight: 500;
|
|
3136
|
+
color: #1f2937;
|
|
3137
|
+
}
|
|
3138
|
+
|
|
3139
|
+
.group-sidebar-close {
|
|
3140
|
+
width: 32px;
|
|
3141
|
+
height: 32px;
|
|
3142
|
+
display: flex;
|
|
3143
|
+
align-items: center;
|
|
3144
|
+
justify-content: center;
|
|
3145
|
+
border-radius: 50%;
|
|
3146
|
+
cursor: pointer;
|
|
3147
|
+
transition: background-color 0.2s;
|
|
3148
|
+
color: #6b7280;
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3151
|
+
.group-sidebar-close:hover {
|
|
3152
|
+
background-color: #f3f4f6;
|
|
3153
|
+
color: #374151;
|
|
3154
|
+
}
|
|
3155
|
+
|
|
3156
|
+
.group-sidebar-content {
|
|
3157
|
+
flex: 1;
|
|
3158
|
+
overflow-y: auto;
|
|
3159
|
+
padding: 16px;
|
|
3160
|
+
}
|
|
3161
|
+
|
|
3162
|
+
.group-members-avatar-section {
|
|
3163
|
+
margin-bottom: 16px;
|
|
3164
|
+
}
|
|
3165
|
+
|
|
3166
|
+
.group-members-grid {
|
|
3167
|
+
display: grid;
|
|
3168
|
+
grid-template-columns: repeat(4, 1fr);
|
|
3169
|
+
gap: 16px;
|
|
3170
|
+
}
|
|
3171
|
+
|
|
3172
|
+
.group-member-avatar-item {
|
|
3173
|
+
display: flex;
|
|
3174
|
+
flex-direction: column;
|
|
3175
|
+
align-items: center;
|
|
3176
|
+
cursor: pointer;
|
|
3177
|
+
padding: 4px;
|
|
3178
|
+
border-radius: 8px;
|
|
3179
|
+
transition: background-color 0.2s;
|
|
3180
|
+
}
|
|
3181
|
+
|
|
3182
|
+
.group-member-avatar-item:hover {
|
|
3183
|
+
background-color: rgba(0, 0, 0, 0.05);
|
|
3184
|
+
}
|
|
3185
|
+
|
|
3186
|
+
.group-member-avatar-small {
|
|
3187
|
+
width: 48px;
|
|
3188
|
+
height: 48px;
|
|
3189
|
+
border-radius: 50%;
|
|
3190
|
+
object-fit: cover;
|
|
3191
|
+
margin-bottom: 4px;
|
|
3192
|
+
}
|
|
3193
|
+
|
|
3194
|
+
.group-member-add-icon {
|
|
3195
|
+
width: 48px;
|
|
3196
|
+
height: 48px;
|
|
3197
|
+
border-radius: 50%;
|
|
3198
|
+
background-color: #f3f4f6;
|
|
3199
|
+
display: flex;
|
|
3200
|
+
align-items: center;
|
|
3201
|
+
justify-content: center;
|
|
3202
|
+
color: #6b7280;
|
|
3203
|
+
margin-bottom: 4px;
|
|
3204
|
+
}
|
|
3205
|
+
|
|
3206
|
+
.group-member-nickname {
|
|
3207
|
+
font-size: 12px;
|
|
3208
|
+
color: #6b7280;
|
|
3209
|
+
text-align: center;
|
|
3210
|
+
overflow: hidden;
|
|
3211
|
+
text-overflow: ellipsis;
|
|
3212
|
+
white-space: nowrap;
|
|
3213
|
+
max-width: 100%;
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
.group-divider {
|
|
3217
|
+
height: 12px;
|
|
3218
|
+
background-color: transparent;
|
|
3219
|
+
border-top: 1px solid #e5e7eb;
|
|
3220
|
+
margin: 16px 0;
|
|
3221
|
+
}
|
|
3222
|
+
|
|
3223
|
+
.group-settings-section {
|
|
3224
|
+
display: flex;
|
|
3225
|
+
flex-direction: column;
|
|
3226
|
+
gap: 4px;
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
.group-setting-item {
|
|
3230
|
+
display: flex;
|
|
3231
|
+
justify-content: space-between;
|
|
3232
|
+
align-items: center;
|
|
3233
|
+
padding: 14px 16px;
|
|
3234
|
+
background-color: white;
|
|
3235
|
+
cursor: pointer;
|
|
3236
|
+
transition: background-color 0.2s;
|
|
3237
|
+
border-radius: 8px;
|
|
3238
|
+
margin-bottom: 4px;
|
|
3239
|
+
}
|
|
3240
|
+
|
|
3241
|
+
.group-setting-item:hover {
|
|
3242
|
+
background-color: #f9fafb;
|
|
3243
|
+
}
|
|
3244
|
+
|
|
3245
|
+
.group-setting-item.danger {
|
|
3246
|
+
color: #ef4444;
|
|
3247
|
+
margin-top: 8px;
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3250
|
+
.group-setting-item.danger:hover {
|
|
3251
|
+
background-color: #fef2f2;
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3254
|
+
.group-setting-label {
|
|
3255
|
+
font-size: 14px;
|
|
3256
|
+
color: #1f2937;
|
|
3257
|
+
}
|
|
3258
|
+
|
|
3259
|
+
.group-setting-item.danger .group-setting-label {
|
|
3260
|
+
color: #ef4444;
|
|
3261
|
+
}
|
|
3262
|
+
|
|
3263
|
+
.group-setting-value {
|
|
3264
|
+
display: flex;
|
|
3265
|
+
align-items: center;
|
|
3266
|
+
gap: 8px;
|
|
3267
|
+
color: #6b7280;
|
|
3268
|
+
font-size: 14px;
|
|
3269
|
+
}
|
|
3270
|
+
|
|
3271
|
+
.group-setting-value-text {
|
|
3272
|
+
max-width: 160px;
|
|
3273
|
+
overflow: hidden;
|
|
3274
|
+
text-overflow: ellipsis;
|
|
3275
|
+
white-space: nowrap;
|
|
3276
|
+
}
|
|
3277
|
+
|
|
3278
|
+
.group-setting-value-wrapper {
|
|
3279
|
+
display: flex;
|
|
3280
|
+
flex: 1;
|
|
3281
|
+
justify-content: flex-end;
|
|
3282
|
+
}
|
|
3283
|
+
|
|
3284
|
+
.group-edit-wrapper {
|
|
3285
|
+
width: 100%;
|
|
3286
|
+
display: flex;
|
|
3287
|
+
flex-direction: column;
|
|
3288
|
+
gap: 12px;
|
|
3289
|
+
}
|
|
3290
|
+
|
|
3291
|
+
.group-edit-buttons {
|
|
3292
|
+
display: flex;
|
|
3293
|
+
justify-content: flex-end;
|
|
3294
|
+
gap: 8px;
|
|
3295
|
+
}
|
|
3296
|
+
|
|
3297
|
+
.group-input-edit {
|
|
3298
|
+
flex: 1;
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
.group-edit-actions {
|
|
3302
|
+
display: flex;
|
|
3303
|
+
gap: 8px;
|
|
3304
|
+
justify-content: flex-end;
|
|
3305
|
+
margin-top: 8px;
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
/* ========== 消息已读成员弹窗样式 ========== */
|
|
3309
|
+
.msg-read-users-content {
|
|
3310
|
+
display: flex;
|
|
3311
|
+
flex-direction: column;
|
|
3312
|
+
gap: 20px;
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3315
|
+
.read-users-section,
|
|
3316
|
+
.unread-users-section {
|
|
3317
|
+
display: flex;
|
|
3318
|
+
flex-direction: column;
|
|
3319
|
+
gap: 12px;
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
.users-list {
|
|
3323
|
+
display: flex;
|
|
3324
|
+
flex-direction: column;
|
|
3325
|
+
gap: 8px;
|
|
3326
|
+
}
|
|
3327
|
+
|
|
3328
|
+
.users-tag-list {
|
|
3329
|
+
display: flex;
|
|
3330
|
+
flex-wrap: wrap;
|
|
3331
|
+
gap: 8px;
|
|
3332
|
+
}
|
|
3333
|
+
|
|
3334
|
+
.user-tag {
|
|
3335
|
+
padding: 6px 12px;
|
|
3336
|
+
background-color: #f3f4f6;
|
|
3337
|
+
border-radius: 9999px;
|
|
3338
|
+
font-size: 13px;
|
|
3339
|
+
color: #374151;
|
|
3340
|
+
|
|
3341
|
+
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
|
3342
|
+
gap: 12px;
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
|
+
.member-item {
|
|
3346
|
+
display: flex;
|
|
3347
|
+
align-items: center;
|
|
3348
|
+
gap: 8px;
|
|
3349
|
+
padding: 8px 12px;
|
|
3350
|
+
border-radius: 8px;
|
|
3351
|
+
cursor: pointer;
|
|
3352
|
+
transition: all 0.2s;
|
|
3353
|
+
border: 2px solid transparent;
|
|
3354
|
+
background-color: white;
|
|
3355
|
+
}
|
|
3356
|
+
|
|
3357
|
+
.member-item:hover {
|
|
3358
|
+
background-color: #f3f4f6;
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
.member-selected {
|
|
3362
|
+
background-color: rgba(7, 193, 96, 0.1);
|
|
3363
|
+
border-color: #07c160;
|
|
3364
|
+
}
|
|
3365
|
+
|
|
3366
|
+
.member-avatar {
|
|
3367
|
+
width: 40px;
|
|
3368
|
+
height: 40px;
|
|
3369
|
+
border-radius: 50%;
|
|
3370
|
+
object-fit: cover;
|
|
3371
|
+
}
|
|
3372
|
+
|
|
3373
|
+
.member-name {
|
|
3374
|
+
flex: 1;
|
|
3375
|
+
font-size: 14px;
|
|
3376
|
+
color: #374151;
|
|
3377
|
+
overflow: hidden;
|
|
3378
|
+
text-overflow: ellipsis;
|
|
3379
|
+
white-space: nowrap;
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
.member-check {
|
|
3383
|
+
color: #07c160;
|
|
3384
|
+
font-size: 20px;
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
.group-detail-content {
|
|
3388
|
+
display: flex;
|
|
3389
|
+
flex-direction: column;
|
|
3390
|
+
gap: 20px;
|
|
3391
|
+
}
|
|
3392
|
+
|
|
3393
|
+
.group-info-section {
|
|
3394
|
+
display: flex;
|
|
3395
|
+
flex-direction: column;
|
|
3396
|
+
gap: 12px;
|
|
3397
|
+
}
|
|
3398
|
+
|
|
3399
|
+
.group-info-item {
|
|
3400
|
+
display: flex;
|
|
3401
|
+
justify-content: space-between;
|
|
3402
|
+
align-items: center;
|
|
3403
|
+
padding: 12px;
|
|
3404
|
+
background-color: #f9fafb;
|
|
3405
|
+
border-radius: 8px;
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3408
|
+
.info-label {
|
|
3409
|
+
font-size: 14px;
|
|
3410
|
+
color: #6b7280;
|
|
3411
|
+
}
|
|
3412
|
+
|
|
3413
|
+
.info-value {
|
|
3414
|
+
font-size: 14px;
|
|
3415
|
+
color: #374151;
|
|
3416
|
+
font-weight: 500;
|
|
3417
|
+
}
|
|
3418
|
+
|
|
3419
|
+
.group-members-section {
|
|
3420
|
+
display: flex;
|
|
3421
|
+
flex-direction: column;
|
|
3422
|
+
gap: 12px;
|
|
3423
|
+
}
|
|
3424
|
+
|
|
3425
|
+
.section-header {
|
|
3426
|
+
display: flex;
|
|
3427
|
+
justify-content: space-between;
|
|
3428
|
+
align-items: center;
|
|
3429
|
+
}
|
|
3430
|
+
|
|
3431
|
+
.section-title {
|
|
3432
|
+
font-size: 14px;
|
|
3433
|
+
font-weight: 500;
|
|
3434
|
+
color: #374151;
|
|
3435
|
+
}
|
|
3436
|
+
|
|
3437
|
+
.group-members-list {
|
|
3438
|
+
display: grid;
|
|
3439
|
+
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
|
3440
|
+
gap: 12px;
|
|
3441
|
+
max-height: 300px;
|
|
3442
|
+
overflow-y: auto;
|
|
3443
|
+
}
|
|
3444
|
+
|
|
3445
|
+
.group-member-item {
|
|
3446
|
+
display: flex;
|
|
3447
|
+
flex-direction: column;
|
|
3448
|
+
align-items: center;
|
|
3449
|
+
gap: 8px;
|
|
3450
|
+
padding: 12px;
|
|
3451
|
+
border-radius: 8px;
|
|
3452
|
+
background-color: #f9fafb;
|
|
3453
|
+
text-align: center;
|
|
3454
|
+
}
|
|
3455
|
+
|
|
3456
|
+
.group-member-avatar {
|
|
3457
|
+
width: 50px;
|
|
3458
|
+
height: 50px;
|
|
3459
|
+
border-radius: 50%;
|
|
3460
|
+
object-fit: cover;
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3463
|
+
.group-member-name {
|
|
3464
|
+
font-size: 12px;
|
|
3465
|
+
color: #374151;
|
|
3466
|
+
overflow: hidden;
|
|
3467
|
+
text-overflow: ellipsis;
|
|
3468
|
+
white-space: nowrap;
|
|
3469
|
+
width: 100%;
|
|
3470
|
+
}
|
|
3471
|
+
|
|
3472
|
+
.invite-member-content {
|
|
3473
|
+
display: flex;
|
|
3474
|
+
flex-direction: column;
|
|
3475
|
+
gap: 16px;
|
|
3476
|
+
}
|
|
2140
3477
|
</style>
|