vue-chat-kit 0.3.9 → 0.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/vue-chat-kit.css +1 -1
  2. package/dist/vue-chat-kit.es.js +4420 -2877
  3. package/dist/vue-chat-kit.umd.js +1 -1
  4. package/package.json +1 -1
  5. package/src/components/AvatarCrop.vue +16 -127
  6. package/src/components/ChatPanel.vue +503 -2818
  7. package/src/components/EmojiPicker.vue +2 -73
  8. package/src/components/chat/ChatWindow.vue +177 -0
  9. package/src/components/chat/ContentList.vue +300 -0
  10. package/src/components/chat/ContextMenu.vue +32 -0
  11. package/src/components/chat/GroupSidebar.vue +284 -0
  12. package/src/components/chat/MainArea.vue +87 -0
  13. package/src/components/chat/Sidebar.vue +52 -0
  14. package/src/components/chat/dialogs/AddFriendDialog.vue +62 -0
  15. package/src/components/chat/dialogs/CreateGroupDialog.vue +86 -0
  16. package/src/components/chat/dialogs/GroupDetailDialog.vue +132 -0
  17. package/src/components/ui/Button.vue +190 -0
  18. package/src/components/ui/Dialog.vue +194 -0
  19. package/src/components/ui/Empty.vue +66 -0
  20. package/src/components/ui/Input.vue +166 -0
  21. package/src/components/ui/Message.vue +186 -0
  22. package/src/components/ui/MessageBox.vue +143 -0
  23. package/src/components/ui/MessageManager.vue +92 -0
  24. package/src/components/ui/Switch.vue +65 -0
  25. package/src/components/ui/Tag.vue +68 -0
  26. package/src/components/ui/icons/ArrowDown.vue +5 -0
  27. package/src/components/ui/icons/ArrowRight.vue +5 -0
  28. package/src/components/ui/icons/Bell.vue +6 -0
  29. package/src/components/ui/icons/Camera.vue +6 -0
  30. package/src/components/ui/icons/ChatDotRound.vue +5 -0
  31. package/src/components/ui/icons/Check.vue +5 -0
  32. package/src/components/ui/icons/CircleCheck.vue +6 -0
  33. package/src/components/ui/icons/Clock.vue +6 -0
  34. package/src/components/ui/icons/Close.vue +5 -0
  35. package/src/components/ui/icons/Delete.vue +8 -0
  36. package/src/components/ui/icons/Edit.vue +6 -0
  37. package/src/components/ui/icons/Folder.vue +5 -0
  38. package/src/components/ui/icons/Minus.vue +5 -0
  39. package/src/components/ui/icons/Monitor.vue +7 -0
  40. package/src/components/ui/icons/Moon.vue +5 -0
  41. package/src/components/ui/icons/Picture.vue +7 -0
  42. package/src/components/ui/icons/Plus.vue +5 -0
  43. package/src/components/ui/icons/Search.vue +6 -0
  44. package/src/components/ui/icons/Setting.vue +6 -0
  45. package/src/components/ui/icons/Sunny.vue +6 -0
  46. package/src/components/ui/icons/User.vue +6 -0
  47. package/src/components/ui/icons/UserFilled.vue +6 -0
  48. package/src/components/ui/icons/Warning.vue +5 -0
  49. package/src/components/ui/icons/index.js +24 -0
  50. package/src/components/ui/index.js +10 -0
  51. package/src/composables/useFriendChat.js +10 -14
  52. package/src/composables/useGroupChat.js +140 -48
  53. package/src/composables/useMessage.js +21 -0
  54. package/src/composables/useMessageBox.js +98 -0
  55. package/src/composables/useTheme.js +140 -0
  56. package/src/config/index.js +1 -0
  57. package/src/const/index.js +1 -0
  58. package/src/const/theme.js +19 -0
  59. package/src/core/api.js +13 -2
  60. package/src/index.js +5 -5
  61. package/src/styles/_base.scss +38 -0
  62. package/src/styles/_variables.scss +43 -0
  63. package/src/styles/components/_add-friend-dialog.scss +45 -0
  64. package/src/styles/components/_avatar-crop.scss +120 -0
  65. package/src/styles/components/_chat-panel.scss +546 -0
  66. package/src/styles/components/_chat-window.scss +239 -0
  67. package/src/styles/components/_content-list.scss +260 -0
  68. package/src/styles/components/_context-menu.scss +35 -0
  69. package/src/styles/components/_create-group-dialog.scss +78 -0
  70. package/src/styles/components/_dialogs.scss +226 -0
  71. package/src/styles/components/_emoji-picker.scss +74 -0
  72. package/src/styles/components/_group-detail-dialog.scss +110 -0
  73. package/src/styles/components/_group-sidebar.scss +278 -0
  74. package/src/styles/components/_main-area.scss +94 -0
  75. package/src/styles/components/_sidebar.scss +83 -0
  76. package/src/styles/index.scss +18 -0
  77. package/src/styles/themes/_dark.scss +68 -0
  78. package/src/styles/themes/_index.scss +7 -0
  79. package/src/styles/themes/_light.scss +69 -0
@@ -0,0 +1,284 @@
1
+ <template>
2
+ <transition name="slide">
3
+ <div v-if="visible" class="group-sidebar">
4
+ <!-- 关闭按钮 -->
5
+ <div class="group-sidebar-header">
6
+ <span class="group-sidebar-title">群聊信息</span>
7
+ <span class="group-sidebar-close" @click="$emit('close')">
8
+ <Close />
9
+ </span>
10
+ </div>
11
+
12
+ <div class="group-sidebar-content">
13
+ <!-- 群成员头像展示区 -->
14
+ <div class="group-members-avatar-section">
15
+ <!-- 移除模式提示 -->
16
+ <div v-if="isRemoveMemberMode" class="group-remove-mode-hint">
17
+ <Warning />
18
+ <span>点击成员头像可移除成员</span>
19
+ </div>
20
+ <div class="group-members-grid">
21
+ <div
22
+ v-for="member in groupMemberList.slice(0, 10)"
23
+ :key="member.username"
24
+ :class="['group-member-avatar-item', { 'is-remove-mode': isRemoveMemberMode }]"
25
+ @click="isRemoveMemberMode ? $emit('remove-member', member.username) : $emit('edit-member-nick', member)"
26
+ >
27
+ <div class="group-member-avatar-wrapper">
28
+ <img
29
+ :src="member.avatar ? `${config.api.baseUrl}${member.avatar}` : `https://api.dicebear.com/7.x/avataaars/svg?seed=${member.username}`"
30
+ :alt="member.username"
31
+ class="group-member-avatar-small"
32
+ />
33
+ <!-- 移除模式下的删除图标 -->
34
+ <div v-if="isRemoveMemberMode && member.username !== currentSelectGroup.owner" class="group-member-remove-badge">
35
+ <Close />
36
+ </div>
37
+ </div>
38
+ <span class="group-member-nickname">{{ member.memberNick || member.username }}</span>
39
+ </div>
40
+
41
+ <!-- 添加按钮 -->
42
+ <div
43
+ class="group-member-avatar-item"
44
+ @click="$emit('invite-member')"
45
+ >
46
+ <div class="group-member-action-icon">
47
+ <Plus />
48
+ </div>
49
+ <span class="group-member-nickname">添加</span>
50
+ </div>
51
+
52
+ <!-- 移除按钮 (仅群主可见) -->
53
+ <div
54
+ v-if="isGroupOwner"
55
+ class="group-member-avatar-item"
56
+ @click="$emit('toggle-remove-mode')"
57
+ :class="{ 'is-active': isRemoveMemberMode }"
58
+ >
59
+ <div class="group-member-action-icon">
60
+ <Minus />
61
+ </div>
62
+ <span class="group-member-nickname">{{ isRemoveMemberMode ? '完成' : '移出' }}</span>
63
+ </div>
64
+ </div>
65
+ </div>
66
+
67
+ <!-- 分割线 -->
68
+ <div class="group-divider"></div>
69
+
70
+ <!-- 群设置项 -->
71
+ <div class="group-settings-section">
72
+ <!-- 群名称 -->
73
+ <div class="group-setting-item" v-if="isGroupOwner">
74
+ <span class="group-setting-label">群聊名称</span>
75
+ <div class="group-setting-value-wrapper">
76
+ <div v-if="editingFields.groupNickname" class="group-edit-wrapper">
77
+ <Input
78
+ :model-value="tempEditValues.groupNickname"
79
+ @update:model-value="$emit('update:tempEditValue', { field: 'groupNickname', value: $event })"
80
+ placeholder="请输入群聊名称"
81
+ @keyup.enter="$emit('save-field', 'groupNickname')"
82
+ class="group-input-edit"
83
+ />
84
+ <div class="group-edit-buttons">
85
+ <Button size="small" @click="$emit('cancel-edit', 'groupNickname')">取消</Button>
86
+ <Button size="small" type="primary" @click="$emit('save-field', 'groupNickname')">保存</Button>
87
+ </div>
88
+ </div>
89
+ <div
90
+ v-else
91
+ class="group-setting-value"
92
+ @click="$emit('start-edit', 'groupNickname')"
93
+ >
94
+ <span class="group-setting-value-text">{{ currentGroupInfo?.groupNickname || currentGroupInfo?.groupName || currentSelectGroup?.name || '' }}</span>
95
+ <span class="group-edit-icon"><Edit /></span>
96
+ </div>
97
+ </div>
98
+ </div>
99
+ <div class="group-setting-item" v-else>
100
+ <span class="group-setting-label">群聊名称</span>
101
+ <span class="group-setting-value-text">{{ currentGroupInfo?.groupNickname || currentGroupInfo?.groupName || currentSelectGroup?.name || '' }}</span>
102
+ </div>
103
+
104
+ <!-- 群公告 -->
105
+ <div class="group-setting-item" v-if="isGroupOwner">
106
+ <span class="group-setting-label">群公告</span>
107
+ <div class="group-setting-value-wrapper">
108
+ <div v-if="editingFields.notice" class="group-edit-wrapper">
109
+ <Input
110
+ :model-value="tempEditValues.notice"
111
+ @update:model-value="$emit('update:tempEditValue', { field: 'notice', value: $event })"
112
+ type="textarea"
113
+ :rows="3"
114
+ placeholder="请输入群公告"
115
+ @keyup.enter.ctrl="$emit('save-field', 'notice')"
116
+ class="group-input-edit"
117
+ />
118
+ <div class="group-edit-buttons">
119
+ <Button size="small" @click="$emit('cancel-edit', 'notice')">取消</Button>
120
+ <Button size="small" type="primary" @click="$emit('save-field', 'notice')">保存</Button>
121
+ </div>
122
+ </div>
123
+ <div
124
+ v-else
125
+ class="group-setting-value"
126
+ @click="$emit('start-edit', 'notice')"
127
+ >
128
+ <span class="group-setting-value-text">{{ currentGroupInfo?.notice || '暂无公告' }}</span>
129
+ <span class="group-edit-icon"><Edit /></span>
130
+ </div>
131
+ </div>
132
+ </div>
133
+ <div class="group-setting-item" v-else>
134
+ <span class="group-setting-label">群公告</span>
135
+ <span class="group-setting-value-text">{{ currentGroupInfo?.notice || '暂无公告' }}</span>
136
+ </div>
137
+
138
+ <!-- 备注 -->
139
+ <div class="group-setting-item" v-if="isGroupOwner">
140
+ <span class="group-setting-label">备注</span>
141
+ <div class="group-setting-value-wrapper">
142
+ <div v-if="editingFields.remark" class="group-edit-wrapper">
143
+ <Input
144
+ :model-value="tempEditValues.remark"
145
+ @update:model-value="$emit('update:tempEditValue', { field: 'remark', value: $event })"
146
+ placeholder="请输入备注"
147
+ @keyup.enter="$emit('save-field', 'remark')"
148
+ class="group-input-edit"
149
+ />
150
+ <div class="group-edit-buttons">
151
+ <Button size="small" @click="$emit('cancel-edit', 'remark')">取消</Button>
152
+ <Button size="small" type="primary" @click="$emit('save-field', 'remark')">保存</Button>
153
+ </div>
154
+ </div>
155
+ <div
156
+ v-else
157
+ class="group-setting-value"
158
+ @click="$emit('start-edit', 'remark')"
159
+ >
160
+ <span class="group-setting-value-text">{{ currentGroupInfo?.remark || '仅自己可见' }}</span>
161
+ <span class="group-edit-icon"><Edit /></span>
162
+ </div>
163
+ </div>
164
+ </div>
165
+ <div class="group-setting-item" v-else>
166
+ <span class="group-setting-label">备注</span>
167
+ <span class="group-setting-value-text">{{ currentGroupInfo?.remark || '群聊的备注仅自己可见' }}</span>
168
+ </div>
169
+
170
+ <!-- 我在本群的昵称 -->
171
+ <div class="group-setting-item" @click="$emit('edit-my-nick')">
172
+ <span class="group-setting-label">我在本群的昵称</span>
173
+ <div class="group-setting-value">
174
+ <span class="group-setting-value-text">{{ myNicknameInGroup || myUsername }}</span>
175
+ <Edit />
176
+ </div>
177
+ </div>
178
+
179
+ <div class="group-divider"></div>
180
+
181
+ <!-- 查找聊天内容 -->
182
+ <div class="group-setting-item">
183
+ <span class="group-setting-label">查找聊天内容</span>
184
+ <ArrowRight />
185
+ </div>
186
+
187
+ <!-- 消息免打扰 -->
188
+ <div class="group-setting-item">
189
+ <span class="group-setting-label">消息免打扰</span>
190
+ <Switch :model-value="muteGroup" @update:model-value="$emit('update:muteGroup', $event)" />
191
+ </div>
192
+
193
+ <div class="group-divider"></div>
194
+
195
+ <!-- 群主专属功能 -->
196
+ <template v-if="isGroupOwner">
197
+ <!-- 转让群主 -->
198
+ <div class="group-setting-item" @click="$emit('show-group-detail')">
199
+ <span class="group-setting-label">转让群主</span>
200
+ <ArrowRight />
201
+ </div>
202
+
203
+ <div class="group-setting-item danger" @click="$emit('delete-group')">
204
+ <span class="group-setting-label">解散群聊</span>
205
+ </div>
206
+ </template>
207
+
208
+ <!-- 普通成员退出 -->
209
+ <template v-else>
210
+ <div class="group-setting-item danger" @click="$emit('quit-group')">
211
+ <span class="group-setting-label">删除并退出</span>
212
+ </div>
213
+ </template>
214
+ </div>
215
+ </div>
216
+ </div>
217
+ </transition>
218
+ </template>
219
+
220
+ <script setup>
221
+ import { computed } from 'vue'
222
+ import { Close, Plus, Minus, Edit, ArrowRight, Warning } from '../ui/icons/index.js'
223
+ import { Input, Button, Switch } from '../ui/index.js'
224
+
225
+ const props = defineProps({
226
+ visible: Boolean,
227
+ currentSelectGroup: Object,
228
+ groupMemberList: Array,
229
+ isRemoveMemberMode: Boolean,
230
+ config: Object,
231
+ myUsername: String,
232
+ editingFields: Object,
233
+ tempEditValues: Object,
234
+ currentGroupInfo: Object,
235
+ myNicknameInGroup: String,
236
+ muteGroup: Boolean,
237
+ groupOwnerUsername: String
238
+ })
239
+
240
+ // 在组件内部直接计算是否群主
241
+ const isGroupOwner = computed(() => {
242
+ console.log('[GroupSidebar] 检查群主身份:', {
243
+ myUsername: props.myUsername,
244
+ groupMemberCount: props.groupMemberList?.length
245
+ })
246
+
247
+ if (!props.groupMemberList || props.groupMemberList.length === 0) {
248
+ console.log('[GroupSidebar] 无成员列表为空')
249
+ return false
250
+ }
251
+
252
+ const currentUserInGroup = props.groupMemberList.find(
253
+ m => m.username === props.myUsername
254
+ )
255
+
256
+ console.log('[GroupSidebar] 当前用户在群成员:', currentUserInGroup)
257
+
258
+ const isOwner = currentUserInGroup?.memberType === 0
259
+
260
+ console.log('[GroupSidebar] 是否群主:', isOwner)
261
+ return isOwner
262
+ })
263
+
264
+ defineEmits([
265
+ 'close',
266
+ 'remove-member',
267
+ 'edit-member-nick',
268
+ 'invite-member',
269
+ 'toggle-remove-mode',
270
+ 'start-edit',
271
+ 'cancel-edit',
272
+ 'save-field',
273
+ 'update:tempEditValue',
274
+ 'edit-my-nick',
275
+ 'update:muteGroup',
276
+ 'show-group-detail',
277
+ 'delete-group',
278
+ 'quit-group'
279
+ ])
280
+ </script>
281
+
282
+ <style scoped lang="scss">
283
+ @use '../../styles/components/group-sidebar';
284
+ </style>
@@ -0,0 +1,87 @@
1
+ <template>
2
+ <div class="chat-main-area">
3
+ <!-- 好友信息展示区域 -->
4
+ <div v-if="currentSelectedFriend && !currentChat && !currentSelectGroup" class="friend-profile-area">
5
+ <img
6
+ :src="currentSelectedFriend.avatar"
7
+ :alt="currentSelectedFriend.name"
8
+ class="profile-avatar"
9
+ />
10
+ <div class="profile-name">{{ currentSelectedFriend.name }}</div>
11
+ <div class="profile-status">
12
+ <span
13
+ :class="[
14
+ 'profile-status-dot',
15
+ currentSelectedFriend.online ? 'profile-status-online' : 'profile-status-offline'
16
+ ]"
17
+ ></span>
18
+ <span>{{ currentSelectedFriend.online ? '在线' : '离线' }}</span>
19
+ </div>
20
+ <el-button
21
+ type="primary"
22
+ size="large"
23
+ @click="$emit('start-chat')"
24
+ class="profile-start-chat-btn"
25
+ >
26
+ <el-icon><ChatDotRound /></el-icon>
27
+ <span>发消息</span>
28
+ </el-button>
29
+ </div>
30
+
31
+ <!-- 群聊信息展示区域 -->
32
+ <div v-if="currentSelectedGroup && !currentSelectGroup" class="friend-profile-area">
33
+ <img
34
+ :src="currentSelectedGroup.avatar"
35
+ :alt="currentSelectedGroup.name"
36
+ class="profile-avatar"
37
+ />
38
+ <div class="profile-name">{{ currentSelectedGroup.name }}</div>
39
+ <div class="profile-status">
40
+ <span class="profile-status-dot profile-status-online"></span>
41
+ <span>{{ currentSelectedGroup.memberCount || 0 }} 人</span>
42
+ </div>
43
+ <el-button
44
+ type="primary"
45
+ size="large"
46
+ @click="$emit('start-group-chat')"
47
+ class="profile-start-chat-btn"
48
+ >
49
+ <el-icon><ChatDotRound /></el-icon>
50
+ <span>发消息</span>
51
+ </el-button>
52
+ </div>
53
+
54
+ <!-- 聊天窗口(群聊或单聊) -->
55
+ <div v-if="currentChat || currentSelectGroup" class="chat-window-area">
56
+ <slot name="chat-window"></slot>
57
+ </div>
58
+
59
+ <!-- 空状态 -->
60
+ <div
61
+ v-else-if="!currentSelectedFriend && !currentSelectedGroup && !currentChat && !currentSelectGroup"
62
+ class="chat-empty-state"
63
+ >
64
+ <el-icon :size="64" class="empty-state-icon"><ChatLineRound /></el-icon>
65
+ <div class="empty-state-text">
66
+ {{ currentNavTab === 'apply' ? '在左侧选择好友申请' : '在左侧选择好友开始聊天' }}
67
+ </div>
68
+ </div>
69
+ </div>
70
+ </template>
71
+
72
+ <script setup>
73
+
74
+ defineProps({
75
+ currentSelectedFriend: Object,
76
+ currentSelectedGroup: Object,
77
+ currentChat: Object,
78
+ currentSelectGroup: Object,
79
+ currentNavTab: String
80
+ })
81
+
82
+ defineEmits(['start-chat', 'start-group-chat'])
83
+ </script>
84
+
85
+ <style scoped lang="scss">
86
+ @use '../../styles/components/main-area';
87
+ </style>
@@ -0,0 +1,52 @@
1
+ <template>
2
+ <div class="chat-sidebar">
3
+ <div class="sidebar-avatar" @click="$emit('avatar-click')">
4
+ <img :src="myAvatar" alt="头像" class="sidebar-avatar-img" />
5
+ </div>
6
+
7
+ <div
8
+ v-for="tab in navTabs"
9
+ :key="tab.id"
10
+ :class="[
11
+ 'sidebar-nav-item',
12
+ currentNavTab === tab.id ? 'sidebar-nav-item-active' : 'sidebar-nav-item-inactive'
13
+ ]"
14
+ @click="$emit('update:currentNavTab', tab.id)"
15
+ >
16
+ <span class="sidebar-icon">
17
+ <component :is="tab.icon" />
18
+ </span>
19
+ <span v-if="tab.badge" class="sidebar-nav-badge">
20
+ {{ tab.badge > 99 ? '99+' : tab.badge }}
21
+ </span>
22
+ </div>
23
+
24
+ <div class="sidebar-spacer"></div>
25
+
26
+ <div
27
+ v-if="config.modules.settings"
28
+ class="sidebar-nav-item sidebar-nav-item-inactive"
29
+ @click="$emit('settings-click')"
30
+ title="设置"
31
+ >
32
+ <span class="sidebar-icon"><Setting /></span>
33
+ </div>
34
+ </div>
35
+ </template>
36
+
37
+ <script setup>
38
+ import { Setting } from '../ui/icons/index.js'
39
+
40
+ defineProps({
41
+ myAvatar: String,
42
+ navTabs: Array,
43
+ currentNavTab: String,
44
+ config: Object
45
+ })
46
+
47
+ defineEmits(['avatar-click', 'settings-click', 'update:currentNavTab'])
48
+ </script>
49
+
50
+ <style scoped lang="scss">
51
+ @use '../../styles/components/sidebar';
52
+ </style>
@@ -0,0 +1,62 @@
1
+ <template>
2
+ <Dialog
3
+ :model-value="visible"
4
+ @update:model-value="$emit('update:visible', $event)"
5
+ title="添加好友"
6
+ width="500px"
7
+ >
8
+ <div class="add-friend-search-wrapper">
9
+ <Input
10
+ :model-value="searchText"
11
+ @update:model-value="$emit('update:searchText', $event)"
12
+ placeholder="搜索用户"
13
+ class="add-friend-search-input"
14
+ >
15
+ <template #prefix>
16
+ <Search />
17
+ </template>
18
+ </Input>
19
+ </div>
20
+ <div class="add-friend-users-list">
21
+ <Empty v-if="loading" description="加载中..." />
22
+ <Empty
23
+ v-else-if="users.length === 0"
24
+ description="暂无用户"
25
+ />
26
+ <div
27
+ v-else
28
+ v-for="user in users"
29
+ :key="user.username"
30
+ class="add-friend-user-item"
31
+ >
32
+ <div class="add-friend-user-info">
33
+ <img
34
+ :src="`https://api.dicebear.com/7.x/avataaars/svg?seed=${user.username}`"
35
+ :alt="user.username"
36
+ class="add-friend-user-avatar"
37
+ />
38
+ <div class="add-friend-user-name">{{ user.username }}</div>
39
+ </div>
40
+ <Button type="primary" size="small" @click="$emit('add', user)">添加</Button>
41
+ </div>
42
+ </div>
43
+ </Dialog>
44
+ </template>
45
+
46
+ <script setup>
47
+ import { Search } from '../../ui/icons/index.js'
48
+ import { Dialog, Input, Empty, Button } from '../../ui/index.js'
49
+
50
+ defineProps({
51
+ visible: Boolean,
52
+ searchText: String,
53
+ loading: Boolean,
54
+ users: Array
55
+ })
56
+
57
+ defineEmits(['update:visible', 'update:searchText', 'add'])
58
+ </script>
59
+
60
+ <style scoped lang="scss">
61
+ @use '../../../styles/components/add-friend-dialog';
62
+ </style>
@@ -0,0 +1,86 @@
1
+ <template>
2
+ <Dialog
3
+ :model-value="visible"
4
+ @update:model-value="$emit('update:visible', $event)"
5
+ title="创建群聊"
6
+ width="600px"
7
+ >
8
+ <div class="create-group-form">
9
+ <div class="form-item">
10
+ <label class="form-label">群名称</label>
11
+ <Input
12
+ :model-value="groupName"
13
+ @update:model-value="$emit('update:groupName', $event)"
14
+ placeholder="请输入群名称"
15
+ maxlength="50"
16
+ />
17
+ </div>
18
+ <div class="form-item">
19
+ <label class="form-label">群备注</label>
20
+ <Input
21
+ :model-value="groupRemark"
22
+ @update:model-value="$emit('update:groupRemark', $event)"
23
+ type="textarea"
24
+ placeholder="请输入群备注(可选)"
25
+ :rows="3"
26
+ maxlength="200"
27
+ />
28
+ </div>
29
+ <div class="form-item">
30
+ <label class="form-label">选择成员</label>
31
+ <div class="member-selection">
32
+ <Empty v-if="friendList.length === 0" description="暂无好友可以添加" />
33
+ <div
34
+ v-else
35
+ class="member-list"
36
+ >
37
+ <div
38
+ v-for="friend in friendList"
39
+ :key="friend.id"
40
+ :class="[
41
+ 'member-item',
42
+ isSelected(friend) ? 'member-selected' : ''
43
+ ]"
44
+ @click="$emit('toggle-member', friend)"
45
+ >
46
+ <img
47
+ :src="friend.avatar"
48
+ :alt="friend.name"
49
+ class="member-avatar"
50
+ />
51
+ <span class="member-name">{{ friend.name }}</span>
52
+ <CircleCheck v-if="isSelected(friend)" class="member-check" />
53
+ </div>
54
+ </div>
55
+ </div>
56
+ </div>
57
+ </div>
58
+ <template #footer>
59
+ <Button @click="$emit('update:visible', false)">取消</Button>
60
+ <Button type="primary" @click="$emit('create')" :disabled="!groupName.trim()">创建</Button>
61
+ </template>
62
+ </Dialog>
63
+ </template>
64
+
65
+ <script setup>
66
+ import { CircleCheck } from '../../ui/icons/index.js'
67
+ import { Dialog, Input, Empty, Button } from '../../ui/index.js'
68
+
69
+ const props = defineProps({
70
+ visible: Boolean,
71
+ groupName: String,
72
+ groupRemark: String,
73
+ friendList: Array,
74
+ selectedMembers: Array
75
+ })
76
+
77
+ defineEmits(['update:visible', 'update:groupName', 'update:groupRemark', 'toggle-member', 'create'])
78
+
79
+ const isSelected = (friend) => {
80
+ return props.selectedMembers.some(m => m.id === friend.id)
81
+ }
82
+ </script>
83
+
84
+ <style scoped lang="scss">
85
+ @use '../../../styles/components/create-group-dialog';
86
+ </style>