vue-chat-kit 0.1.1 → 0.1.3
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 -0
- package/dist/vue-chat-kit.es.js +1864 -0
- package/dist/vue-chat-kit.umd.js +1 -0
- package/package.json +3 -3
- package/src/components/ChatWindow.vue +1158 -390
- package/src/index.js +3 -0
- package/src/style.css +3 -0
|
@@ -8,38 +8,35 @@
|
|
|
8
8
|
@closed="handleClosed"
|
|
9
9
|
@open="handleOpen"
|
|
10
10
|
>
|
|
11
|
-
<div class="chat-container
|
|
11
|
+
<div class="chat-container">
|
|
12
12
|
<!-- 左侧图标导航栏 -->
|
|
13
|
-
<div class="
|
|
14
|
-
<div class="
|
|
15
|
-
<img :src="myAvatar" alt="头像" class="
|
|
13
|
+
<div class="sidebar-nav">
|
|
14
|
+
<div class="sidebar-avatar" @click="handleAvatarClick">
|
|
15
|
+
<img :src="myAvatar" alt="头像" class="avatar-img" />
|
|
16
16
|
</div>
|
|
17
17
|
|
|
18
18
|
<div
|
|
19
19
|
v-for="tab in navTabs"
|
|
20
20
|
:key="tab.id"
|
|
21
21
|
:class="[
|
|
22
|
-
'
|
|
23
|
-
currentNavTab === tab.id ? '
|
|
22
|
+
'nav-item',
|
|
23
|
+
currentNavTab === tab.id ? 'nav-item-active' : 'nav-item-inactive'
|
|
24
24
|
]"
|
|
25
25
|
@click="currentNavTab = tab.id"
|
|
26
26
|
>
|
|
27
27
|
<el-icon :size="24">
|
|
28
28
|
<component :is="tab.icon" />
|
|
29
29
|
</el-icon>
|
|
30
|
-
<span
|
|
31
|
-
v-if="tab.badge"
|
|
32
|
-
class="absolute -top-1 -right-1 w-4 h-4 bg-red-500 rounded-full text-xs text-white flex items-center justify-center"
|
|
33
|
-
>
|
|
30
|
+
<span v-if="tab.badge" class="nav-badge">
|
|
34
31
|
{{ tab.badge > 99 ? '99+' : tab.badge }}
|
|
35
32
|
</span>
|
|
36
33
|
</div>
|
|
37
34
|
|
|
38
|
-
<div class="
|
|
35
|
+
<div class="nav-spacer"></div>
|
|
39
36
|
|
|
40
37
|
<div
|
|
41
38
|
v-if="config.modules.settings"
|
|
42
|
-
class="
|
|
39
|
+
class="nav-item nav-item-inactive"
|
|
43
40
|
@click="showSettingsDialog = true"
|
|
44
41
|
title="设置"
|
|
45
42
|
>
|
|
@@ -48,9 +45,9 @@
|
|
|
48
45
|
</div>
|
|
49
46
|
|
|
50
47
|
<!-- 中间内容栏 -->
|
|
51
|
-
<div class="
|
|
48
|
+
<div class="content-panel">
|
|
52
49
|
<!-- 搜索栏 -->
|
|
53
|
-
<div class="
|
|
50
|
+
<div class="search-bar">
|
|
54
51
|
<el-input
|
|
55
52
|
v-model="searchText"
|
|
56
53
|
placeholder="搜索"
|
|
@@ -59,40 +56,40 @@
|
|
|
59
56
|
</div>
|
|
60
57
|
|
|
61
58
|
<!-- 内容区域 -->
|
|
62
|
-
<div class="
|
|
59
|
+
<div class="content-scroll">
|
|
63
60
|
<!-- 聊天列表 -->
|
|
64
61
|
<div v-if="currentNavTab === 'chat'">
|
|
65
62
|
<div
|
|
66
63
|
v-for="chat in filteredUsers"
|
|
67
64
|
:key="chat.id"
|
|
68
65
|
:class="[
|
|
69
|
-
'
|
|
70
|
-
currentChatId === chat.id ? '
|
|
66
|
+
'chat-item',
|
|
67
|
+
currentChatId === chat.id ? 'chat-item-active' : ''
|
|
71
68
|
]"
|
|
72
69
|
@click="selectChat(chat)"
|
|
73
70
|
@contextmenu.prevent.stop="showContextMenu($event, chat)"
|
|
74
71
|
>
|
|
75
|
-
<div class="
|
|
72
|
+
<div class="friend-avatar-wrapper">
|
|
76
73
|
<img
|
|
77
74
|
:src="chat.avatar"
|
|
78
75
|
:alt="chat.name"
|
|
79
|
-
class="
|
|
76
|
+
class="friend-avatar"
|
|
80
77
|
/>
|
|
81
78
|
<span
|
|
82
79
|
v-if="chat.online"
|
|
83
|
-
class="
|
|
80
|
+
class="online-indicator"
|
|
84
81
|
></span>
|
|
85
82
|
</div>
|
|
86
|
-
<div class="
|
|
87
|
-
<div class="
|
|
88
|
-
<span class="
|
|
89
|
-
<span class="
|
|
83
|
+
<div class="friend-info">
|
|
84
|
+
<div class="friend-header">
|
|
85
|
+
<span class="friend-name">{{ chat.name }}</span>
|
|
86
|
+
<span class="last-time">{{ formatLastTime(chat.lastTime) }}</span>
|
|
90
87
|
</div>
|
|
91
|
-
<div class="
|
|
92
|
-
<span class="
|
|
88
|
+
<div class="friend-preview">
|
|
89
|
+
<span class="last-msg">{{ chat.lastMsg }}</span>
|
|
93
90
|
<span
|
|
94
91
|
v-if="chat.unread > 0"
|
|
95
|
-
class="
|
|
92
|
+
class="unread-badge"
|
|
96
93
|
>
|
|
97
94
|
{{ chat.unread > 99 ? '99+' : chat.unread }}
|
|
98
95
|
</span>
|
|
@@ -103,40 +100,38 @@
|
|
|
103
100
|
|
|
104
101
|
<!-- 好友列表 -->
|
|
105
102
|
<div v-if="currentNavTab === 'friends' && config.modules.friends">
|
|
106
|
-
<div class="
|
|
103
|
+
<div class="add-friend-section">
|
|
107
104
|
<div
|
|
108
|
-
class="
|
|
105
|
+
class="add-friend-btn"
|
|
109
106
|
@click="openAddFriendDialog"
|
|
110
107
|
>
|
|
111
|
-
<div
|
|
112
|
-
class="w-11 h-11 bg-green-500 rounded-lg flex items-center justify-center"
|
|
113
|
-
>
|
|
108
|
+
<div class="add-friend-icon">
|
|
114
109
|
<el-icon class="text-white" :size="20"><Plus /></el-icon>
|
|
115
110
|
</div>
|
|
116
|
-
<span class="
|
|
111
|
+
<span class="add-friend-text">添加好友</span>
|
|
117
112
|
</div>
|
|
118
113
|
</div>
|
|
119
114
|
<div
|
|
120
115
|
v-for="friend in filteredFriendList"
|
|
121
116
|
:key="friend.id"
|
|
122
|
-
class="
|
|
117
|
+
class="chat-item"
|
|
123
118
|
@click="selectFriend(friend)"
|
|
124
119
|
>
|
|
125
|
-
<div class="
|
|
120
|
+
<div class="friend-avatar-wrapper">
|
|
126
121
|
<img
|
|
127
122
|
:src="friend.avatar"
|
|
128
123
|
:alt="friend.name"
|
|
129
|
-
class="
|
|
124
|
+
class="friend-avatar"
|
|
130
125
|
/>
|
|
131
126
|
<span
|
|
132
127
|
:class="[
|
|
133
|
-
'
|
|
134
|
-
friend.online ? '
|
|
128
|
+
'online-indicator',
|
|
129
|
+
friend.online ? 'online' : 'offline'
|
|
135
130
|
]"
|
|
136
131
|
></span>
|
|
137
132
|
</div>
|
|
138
|
-
<div class="
|
|
139
|
-
<span class="
|
|
133
|
+
<div class="friend-info">
|
|
134
|
+
<span class="friend-name">{{ friend.name }}</span>
|
|
140
135
|
</div>
|
|
141
136
|
</div>
|
|
142
137
|
</div>
|
|
@@ -152,17 +147,17 @@
|
|
|
152
147
|
v-else
|
|
153
148
|
v-for="apply in friendApplyList"
|
|
154
149
|
:key="apply.applyUser || apply.id"
|
|
155
|
-
class="
|
|
150
|
+
class="friend-request-item"
|
|
156
151
|
>
|
|
157
|
-
<div class="
|
|
152
|
+
<div class="request-info">
|
|
158
153
|
<img
|
|
159
154
|
:src="`https://api.dicebear.com/7.x/avataaars/svg?seed=${apply.applyUser}`"
|
|
160
155
|
:alt="apply.applyUser"
|
|
161
|
-
class="
|
|
156
|
+
class="request-avatar"
|
|
162
157
|
/>
|
|
163
|
-
<div class="
|
|
164
|
-
<div class="
|
|
165
|
-
<div class="
|
|
158
|
+
<div class="request-details">
|
|
159
|
+
<div class="request-username">{{ apply.applyUser }}</div>
|
|
160
|
+
<div class="request-desc">请求添加你为好友</div>
|
|
166
161
|
</div>
|
|
167
162
|
</div>
|
|
168
163
|
<el-button
|
|
@@ -176,58 +171,54 @@
|
|
|
176
171
|
</div>
|
|
177
172
|
|
|
178
173
|
<!-- 右侧聊天/详情区域 -->
|
|
179
|
-
<div class="
|
|
174
|
+
<div class="chat-area">
|
|
180
175
|
<!-- 好友信息展示区域 -->
|
|
181
|
-
<div v-if="currentSelectedFriend && !currentChat" class="
|
|
182
|
-
<
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
]"
|
|
197
|
-
></span>
|
|
198
|
-
<span class="text-sm text-gray-500">{{ currentSelectedFriend.online ? '在线' : '离线' }}</span>
|
|
199
|
-
</div>
|
|
200
|
-
<el-button
|
|
201
|
-
type="primary"
|
|
202
|
-
size="large"
|
|
203
|
-
@click="handleStartChat"
|
|
204
|
-
class="w-40"
|
|
205
|
-
>
|
|
206
|
-
<el-icon><ChatDotRound /></el-icon>
|
|
207
|
-
<span>发消息</span>
|
|
208
|
-
</el-button>
|
|
176
|
+
<div v-if="currentSelectedFriend && !currentChat" class="friend-profile">
|
|
177
|
+
<img
|
|
178
|
+
:src="currentSelectedFriend.avatar"
|
|
179
|
+
:alt="currentSelectedFriend.name"
|
|
180
|
+
class="profile-avatar"
|
|
181
|
+
/>
|
|
182
|
+
<div class="profile-name">{{ currentSelectedFriend.name }}</div>
|
|
183
|
+
<div class="profile-status">
|
|
184
|
+
<span
|
|
185
|
+
:class="[
|
|
186
|
+
'status-dot',
|
|
187
|
+
currentSelectedFriend.online ? 'status-online' : 'status-offline'
|
|
188
|
+
]"
|
|
189
|
+
></span>
|
|
190
|
+
<span>{{ currentSelectedFriend.online ? '在线' : '离线' }}</span>
|
|
209
191
|
</div>
|
|
192
|
+
<el-button
|
|
193
|
+
type="primary"
|
|
194
|
+
size="large"
|
|
195
|
+
@click="handleStartChat"
|
|
196
|
+
class="start-chat-btn"
|
|
197
|
+
>
|
|
198
|
+
<el-icon><ChatDotRound /></el-icon>
|
|
199
|
+
<span>发消息</span>
|
|
200
|
+
</el-button>
|
|
210
201
|
</div>
|
|
211
202
|
|
|
212
203
|
<!-- 聊天窗口 -->
|
|
213
|
-
<div v-if="currentChat" class="
|
|
204
|
+
<div v-if="currentChat" class="chat-window">
|
|
214
205
|
<!-- 顶部标题栏 -->
|
|
215
|
-
<div class="
|
|
216
|
-
<div class="
|
|
217
|
-
<span class="
|
|
206
|
+
<div class="chat-header">
|
|
207
|
+
<div class="chat-title">
|
|
208
|
+
<span class="chat-name">{{ currentChat.name }}</span>
|
|
218
209
|
<span
|
|
219
210
|
:class="[
|
|
220
|
-
'
|
|
221
|
-
currentChat.online ? '
|
|
211
|
+
'status-badge',
|
|
212
|
+
currentChat.online ? 'status-badge-online' : 'status-badge-offline'
|
|
222
213
|
]"
|
|
223
214
|
>
|
|
224
215
|
{{ currentChat.online ? '在线' : '离线' }}
|
|
225
216
|
</span>
|
|
226
217
|
</div>
|
|
227
|
-
<div class="
|
|
228
|
-
<el-icon class="
|
|
218
|
+
<div class="chat-actions">
|
|
219
|
+
<el-icon class="action-icon"><Search /></el-icon>
|
|
229
220
|
<el-icon
|
|
230
|
-
class="
|
|
221
|
+
class="action-icon"
|
|
231
222
|
@click="showChatDetail = !showChatDetail"
|
|
232
223
|
><MoreFilled /></el-icon>
|
|
233
224
|
</div>
|
|
@@ -236,40 +227,40 @@
|
|
|
236
227
|
<!-- 聊天消息区域 -->
|
|
237
228
|
<div
|
|
238
229
|
ref="messagesContainer"
|
|
239
|
-
class="
|
|
230
|
+
class="messages-container"
|
|
240
231
|
>
|
|
241
232
|
<div
|
|
242
233
|
v-for="(msg, index) in currentMessages"
|
|
243
234
|
:key="index"
|
|
244
235
|
:class="[
|
|
245
|
-
'
|
|
246
|
-
msg.isSelf ? '
|
|
236
|
+
'message-wrapper',
|
|
237
|
+
msg.isSelf ? 'message-self' : 'message-other'
|
|
247
238
|
]"
|
|
248
239
|
>
|
|
249
240
|
<!-- 头像 -->
|
|
250
|
-
<div class="
|
|
241
|
+
<div class="message-avatar">
|
|
251
242
|
<img
|
|
252
243
|
:src="msg.isSelf ? myAvatar : currentChat.avatar"
|
|
253
|
-
class="
|
|
244
|
+
class="avatar-sm"
|
|
254
245
|
/>
|
|
255
246
|
</div>
|
|
256
247
|
|
|
257
248
|
<!-- 消息内容 -->
|
|
258
249
|
<div
|
|
259
250
|
:class="[
|
|
260
|
-
'
|
|
261
|
-
msg.isSelf ? '
|
|
251
|
+
'message-content-wrapper',
|
|
252
|
+
msg.isSelf ? 'content-self' : 'content-other'
|
|
262
253
|
]"
|
|
263
254
|
>
|
|
264
|
-
<div v-if="!msg.isSelf" class="
|
|
255
|
+
<div v-if="!msg.isSelf" class="sender-name">{{ currentChat.name }}</div>
|
|
265
256
|
|
|
266
|
-
<div class="
|
|
257
|
+
<div class="message-bubble-wrapper">
|
|
267
258
|
<!-- 文本消息 -->
|
|
268
259
|
<div
|
|
269
260
|
v-if="msg.type === 'text'"
|
|
270
261
|
:class="[
|
|
271
|
-
'
|
|
272
|
-
msg.isSelf ? '
|
|
262
|
+
'message-bubble',
|
|
263
|
+
msg.isSelf ? 'bubble-self' : 'bubble-other'
|
|
273
264
|
]"
|
|
274
265
|
>
|
|
275
266
|
{{ msg.text }}
|
|
@@ -279,65 +270,38 @@
|
|
|
279
270
|
<div
|
|
280
271
|
v-else-if="msg.type === 'file' && msg.fileType === 'image'"
|
|
281
272
|
:class="[
|
|
282
|
-
'
|
|
283
|
-
|
|
273
|
+
'message-bubble',
|
|
274
|
+
'image-bubble',
|
|
275
|
+
msg.isSelf ? 'bubble-self' : 'bubble-other'
|
|
284
276
|
]"
|
|
285
277
|
@click="openFile(msg.fileUrl)"
|
|
286
278
|
>
|
|
287
279
|
<img
|
|
288
280
|
:src="msg.fileUrl"
|
|
289
281
|
:alt="msg.fileName"
|
|
290
|
-
class="
|
|
282
|
+
class="message-image"
|
|
291
283
|
@error="handleImageError"
|
|
292
284
|
/>
|
|
293
|
-
<div
|
|
294
|
-
v-if="msg.fileName || msg.fileSize"
|
|
295
|
-
:class="[
|
|
296
|
-
msg.isSelf ? 'bg-[#95ec69] text-gray-700' : 'bg-white text-gray-500'
|
|
297
|
-
]"
|
|
298
|
-
>
|
|
299
|
-
<div class="absolute left-1 bottom-0 text-white" v-if="msg.fileSize">{{ formatFileSize(msg.fileSize) }}</div>
|
|
300
|
-
</div>
|
|
285
|
+
<div v-if="msg.fileSize" class="image-size">{{ formatFileSize(msg.fileSize) }}</div>
|
|
301
286
|
</div>
|
|
302
287
|
|
|
303
288
|
<!-- 文档文件消息 -->
|
|
304
289
|
<div
|
|
305
290
|
v-else-if="msg.type === 'file'"
|
|
306
291
|
:class="[
|
|
307
|
-
'
|
|
308
|
-
|
|
292
|
+
'message-bubble',
|
|
293
|
+
'file-bubble',
|
|
294
|
+
msg.isSelf ? 'bubble-self' : 'bubble-other'
|
|
309
295
|
]"
|
|
310
296
|
@click="openFile(msg.fileUrl)"
|
|
311
297
|
>
|
|
312
|
-
<div
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
msg.isSelf ? 'bg-[#95ec69]' : 'bg-white'
|
|
316
|
-
]"
|
|
317
|
-
>
|
|
318
|
-
<!-- 文件图标 -->
|
|
319
|
-
<div class="w-10 h-10 flex items-center justify-center rounded-lg flex-shrink-0">
|
|
320
|
-
<el-icon :size="28" :class="msg.isSelf ? 'text-gray-700' : 'text-gray-500'">
|
|
321
|
-
<Document />
|
|
322
|
-
</el-icon>
|
|
298
|
+
<div class="file-content">
|
|
299
|
+
<div class="file-icon">
|
|
300
|
+
<el-icon :size="28"><Document /></el-icon>
|
|
323
301
|
</div>
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
<div
|
|
328
|
-
:class="[
|
|
329
|
-
'truncate text-sm font-medium leading-tight',
|
|
330
|
-
msg.isSelf ? 'text-gray-800' : 'text-gray-800'
|
|
331
|
-
]"
|
|
332
|
-
>
|
|
333
|
-
{{ msg.fileName || msg.text }}
|
|
334
|
-
</div>
|
|
335
|
-
<div
|
|
336
|
-
:class="[
|
|
337
|
-
'text-xs mt-1 flex items-center gap-2',
|
|
338
|
-
msg.isSelf ? 'text-gray-600' : 'text-gray-500'
|
|
339
|
-
]"
|
|
340
|
-
>
|
|
302
|
+
<div class="file-info">
|
|
303
|
+
<div class="file-name">{{ msg.fileName || msg.text }}</div>
|
|
304
|
+
<div class="file-meta">
|
|
341
305
|
<el-icon :size="12"><Download /></el-icon>
|
|
342
306
|
<span>点击下载</span>
|
|
343
307
|
<span v-if="msg.fileSize">· {{ formatFileSize(msg.fileSize) }}</span>
|
|
@@ -349,8 +313,8 @@
|
|
|
349
313
|
<!-- 时间显示在气泡下方 -->
|
|
350
314
|
<div
|
|
351
315
|
:class="[
|
|
352
|
-
'
|
|
353
|
-
msg.isSelf ? '
|
|
316
|
+
'message-time',
|
|
317
|
+
msg.isSelf ? 'time-right' : 'time-left'
|
|
354
318
|
]"
|
|
355
319
|
>
|
|
356
320
|
{{ formatTime(msg.time) }}
|
|
@@ -361,30 +325,30 @@
|
|
|
361
325
|
</div>
|
|
362
326
|
|
|
363
327
|
<!-- 底部输入区域 -->
|
|
364
|
-
<div class="
|
|
328
|
+
<div class="input-area">
|
|
365
329
|
<!-- 待发送文件预览 -->
|
|
366
330
|
<div
|
|
367
331
|
v-if="pendingFiles.length > 0"
|
|
368
|
-
class="
|
|
332
|
+
class="pending-files"
|
|
369
333
|
>
|
|
370
334
|
<div
|
|
371
335
|
v-for="(file, index) in pendingFiles"
|
|
372
336
|
:key="file.id"
|
|
373
|
-
class="
|
|
337
|
+
class="pending-file"
|
|
374
338
|
>
|
|
375
339
|
<!-- 图片预览 -->
|
|
376
340
|
<div
|
|
377
341
|
v-if="file.isImage"
|
|
378
|
-
class="
|
|
342
|
+
class="pending-image-wrapper"
|
|
379
343
|
>
|
|
380
344
|
<img
|
|
381
345
|
:src="file.previewUrl"
|
|
382
346
|
:alt="file.name"
|
|
383
|
-
class="
|
|
347
|
+
class="pending-image"
|
|
384
348
|
/>
|
|
385
349
|
<button
|
|
386
350
|
@click="removePendingFile(index)"
|
|
387
|
-
class="
|
|
351
|
+
class="remove-file-btn"
|
|
388
352
|
>
|
|
389
353
|
×
|
|
390
354
|
</button>
|
|
@@ -392,13 +356,13 @@
|
|
|
392
356
|
<!-- 非图片文件预览 -->
|
|
393
357
|
<div
|
|
394
358
|
v-else
|
|
395
|
-
class="
|
|
359
|
+
class="pending-file-wrapper"
|
|
396
360
|
>
|
|
397
|
-
<el-icon class="
|
|
398
|
-
<span class="
|
|
361
|
+
<el-icon class="pending-file-icon"><Folder /></el-icon>
|
|
362
|
+
<span class="pending-file-name">{{ file.name }}</span>
|
|
399
363
|
<button
|
|
400
364
|
@click="removePendingFile(index)"
|
|
401
|
-
class="
|
|
365
|
+
class="remove-file-btn"
|
|
402
366
|
>
|
|
403
367
|
×
|
|
404
368
|
</button>
|
|
@@ -406,30 +370,30 @@
|
|
|
406
370
|
</div>
|
|
407
371
|
</div>
|
|
408
372
|
|
|
409
|
-
<div v-if="config.modules.fileUpload" class="
|
|
410
|
-
<el-icon class="
|
|
373
|
+
<div v-if="config.modules.fileUpload" class="input-actions">
|
|
374
|
+
<el-icon class="action-icon"><ChatDotRound /></el-icon>
|
|
411
375
|
<el-icon
|
|
412
|
-
class="
|
|
376
|
+
class="action-icon"
|
|
413
377
|
@click="triggerFileSelect"
|
|
414
378
|
><Folder /></el-icon>
|
|
415
|
-
<el-icon class="
|
|
379
|
+
<el-icon class="action-icon"><Picture /></el-icon>
|
|
416
380
|
</div>
|
|
417
|
-
<div class="
|
|
381
|
+
<div class="input-wrapper">
|
|
418
382
|
<textarea
|
|
419
383
|
v-model="inputText"
|
|
420
384
|
@keydown.enter.prevent="handleSend"
|
|
421
385
|
@paste="handlePaste"
|
|
422
386
|
placeholder="输入消息或粘贴文件..."
|
|
423
|
-
class="
|
|
387
|
+
class="message-input"
|
|
424
388
|
rows="3"
|
|
425
389
|
/>
|
|
426
390
|
</div>
|
|
427
|
-
<div class="
|
|
391
|
+
<div class="send-btn-wrapper">
|
|
428
392
|
<el-button
|
|
429
393
|
type="primary"
|
|
430
394
|
:disabled="!inputText.trim() && pendingFiles.length === 0"
|
|
431
395
|
@click="handleSend"
|
|
432
|
-
class="
|
|
396
|
+
class="send-btn"
|
|
433
397
|
>
|
|
434
398
|
发送
|
|
435
399
|
</el-button>
|
|
@@ -440,7 +404,7 @@
|
|
|
440
404
|
ref="fileInputRef"
|
|
441
405
|
type="file"
|
|
442
406
|
multiple
|
|
443
|
-
class="hidden"
|
|
407
|
+
class="hidden-file-input"
|
|
444
408
|
@change="handleFileSelect"
|
|
445
409
|
/>
|
|
446
410
|
</div>
|
|
@@ -449,10 +413,10 @@
|
|
|
449
413
|
<!-- 空状态 -->
|
|
450
414
|
<div
|
|
451
415
|
v-else-if="!currentSelectedFriend"
|
|
452
|
-
class="
|
|
416
|
+
class="empty-state"
|
|
453
417
|
>
|
|
454
|
-
<el-icon :size="64" class="
|
|
455
|
-
<div class="text
|
|
418
|
+
<el-icon :size="64" class="empty-icon"><ChatLineRound /></el-icon>
|
|
419
|
+
<div class="empty-text">
|
|
456
420
|
{{ currentNavTab === 'apply' ? '在左侧选择好友申请' : '在左侧选择好友开始聊天' }}
|
|
457
421
|
</div>
|
|
458
422
|
</div>
|
|
@@ -461,28 +425,20 @@
|
|
|
461
425
|
<!-- 右侧详情面板 -->
|
|
462
426
|
<div
|
|
463
427
|
v-if="showChatDetail"
|
|
464
|
-
class="
|
|
428
|
+
class="detail-panel"
|
|
465
429
|
>
|
|
466
|
-
<div class="
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
<div class="flex-1 p-4">
|
|
470
|
-
<div class="flex flex-col items-center">
|
|
430
|
+
<div class="detail-header">聊天详情</div>
|
|
431
|
+
<div class="detail-content">
|
|
432
|
+
<div class="detail-profile">
|
|
471
433
|
<img
|
|
472
434
|
:src="currentChat?.avatar"
|
|
473
435
|
:alt="currentChat?.name"
|
|
474
|
-
class="
|
|
436
|
+
class="detail-avatar"
|
|
475
437
|
/>
|
|
476
|
-
<div class="
|
|
477
|
-
<div class="
|
|
478
|
-
<div class="
|
|
479
|
-
|
|
480
|
-
<span class="text-sm text-gray-700">查找聊天记录</span>
|
|
481
|
-
</div>
|
|
482
|
-
<div class="p-3 border-b border-gray-100 cursor-pointer hover:bg-gray-50">
|
|
483
|
-
<span class="text-sm text-gray-700">清空聊天记录</span>
|
|
484
|
-
</div>
|
|
485
|
-
</div>
|
|
438
|
+
<div class="detail-name">{{ currentChat?.name }}</div>
|
|
439
|
+
<div class="detail-actions">
|
|
440
|
+
<div class="detail-action-item">查找聊天记录</div>
|
|
441
|
+
<div class="detail-action-item">清空聊天记录</div>
|
|
486
442
|
</div>
|
|
487
443
|
</div>
|
|
488
444
|
</div>
|
|
@@ -496,8 +452,8 @@
|
|
|
496
452
|
width="500px"
|
|
497
453
|
append-to-body
|
|
498
454
|
>
|
|
499
|
-
<div class="
|
|
500
|
-
<div class="
|
|
455
|
+
<div class="search-users-wrapper">
|
|
456
|
+
<div class="search-users-input">
|
|
501
457
|
<el-input
|
|
502
458
|
v-model="addFriendSearchText"
|
|
503
459
|
placeholder="搜索用户"
|
|
@@ -505,7 +461,7 @@
|
|
|
505
461
|
/>
|
|
506
462
|
</div>
|
|
507
463
|
</div>
|
|
508
|
-
<div class="
|
|
464
|
+
<div class="users-list-scroll">
|
|
509
465
|
<el-empty v-if="loadingAvailableUsers" description="加载中..." />
|
|
510
466
|
<el-empty
|
|
511
467
|
v-else-if="filteredAvailableUsers.length === 0"
|
|
@@ -515,17 +471,15 @@
|
|
|
515
471
|
v-else
|
|
516
472
|
v-for="user in filteredAvailableUsers"
|
|
517
473
|
:key="user.username"
|
|
518
|
-
class="
|
|
474
|
+
class="available-user-item"
|
|
519
475
|
>
|
|
520
|
-
<div class="
|
|
476
|
+
<div class="available-user-info">
|
|
521
477
|
<img
|
|
522
478
|
:src="`https://api.dicebear.com/7.x/avataaars/svg?seed=${user.username}`"
|
|
523
479
|
:alt="user.username"
|
|
524
|
-
class="
|
|
480
|
+
class="available-user-avatar"
|
|
525
481
|
/>
|
|
526
|
-
<div class="
|
|
527
|
-
<div class="font-medium text-gray-800">{{ user.username }}</div>
|
|
528
|
-
</div>
|
|
482
|
+
<div class="available-user-name">{{ user.username }}</div>
|
|
529
483
|
</div>
|
|
530
484
|
<el-button type="primary" size="small" @click="addFriend(user)">添加</el-button>
|
|
531
485
|
</div>
|
|
@@ -541,40 +495,40 @@
|
|
|
541
495
|
append-to-body
|
|
542
496
|
class="settings-dialog"
|
|
543
497
|
>
|
|
544
|
-
<div class="
|
|
498
|
+
<div class="settings-container">
|
|
545
499
|
<!-- 头像设置区域 -->
|
|
546
|
-
<div class="
|
|
547
|
-
<div class="
|
|
500
|
+
<div class="settings-avatar-section">
|
|
501
|
+
<div class="settings-avatar-wrapper">
|
|
548
502
|
<img
|
|
549
503
|
:src="myAvatar"
|
|
550
504
|
alt="头像"
|
|
551
|
-
class="
|
|
505
|
+
class="settings-avatar"
|
|
552
506
|
/>
|
|
553
507
|
<div
|
|
554
508
|
v-if="config.modules.avatarCrop"
|
|
555
|
-
class="
|
|
509
|
+
class="settings-avatar-edit"
|
|
556
510
|
@click="triggerAvatarUpload"
|
|
557
511
|
>
|
|
558
|
-
<el-icon :size="18" class="
|
|
512
|
+
<el-icon :size="18" class="settings-avatar-icon"><Camera /></el-icon>
|
|
559
513
|
</div>
|
|
560
514
|
<input
|
|
561
515
|
ref="avatarInputRef"
|
|
562
516
|
type="file"
|
|
563
517
|
accept="image/*"
|
|
564
|
-
class="hidden"
|
|
518
|
+
class="hidden-avatar-input"
|
|
565
519
|
@change="handleAvatarFileChange"
|
|
566
520
|
/>
|
|
567
521
|
</div>
|
|
568
|
-
<div class="
|
|
569
|
-
<div class="
|
|
570
|
-
<div class="
|
|
522
|
+
<div class="settings-user-display">
|
|
523
|
+
<div class="settings-nickname">{{ userInfo.nickname || myUsername }}</div>
|
|
524
|
+
<div class="settings-username">@{{ myUsername }}</div>
|
|
571
525
|
</div>
|
|
572
526
|
</div>
|
|
573
527
|
|
|
574
528
|
<!-- 用户信息表单 -->
|
|
575
|
-
<div class="
|
|
576
|
-
<div class="
|
|
577
|
-
<div class="
|
|
529
|
+
<div class="settings-form-section">
|
|
530
|
+
<div class="settings-form-header">
|
|
531
|
+
<div class="settings-form-title">
|
|
578
532
|
<el-icon><UserFilled /></el-icon>
|
|
579
533
|
个人信息
|
|
580
534
|
</div>
|
|
@@ -583,16 +537,16 @@
|
|
|
583
537
|
type="primary"
|
|
584
538
|
size="small"
|
|
585
539
|
@click="startEditUserInfo"
|
|
586
|
-
class="
|
|
540
|
+
class="settings-edit-btn"
|
|
587
541
|
>
|
|
588
542
|
编辑
|
|
589
543
|
</el-button>
|
|
590
544
|
</div>
|
|
591
545
|
|
|
592
|
-
<div class="
|
|
546
|
+
<div class="settings-form">
|
|
593
547
|
<!-- 昵称 -->
|
|
594
|
-
<div>
|
|
595
|
-
<label class="
|
|
548
|
+
<div class="settings-form-item">
|
|
549
|
+
<label class="settings-form-label">昵称</label>
|
|
596
550
|
<el-input
|
|
597
551
|
v-if="isEditingUserInfo"
|
|
598
552
|
v-model="editingUserInfo.nickname"
|
|
@@ -601,15 +555,15 @@
|
|
|
601
555
|
/>
|
|
602
556
|
<div
|
|
603
557
|
v-else
|
|
604
|
-
class="
|
|
558
|
+
class="settings-form-value"
|
|
605
559
|
>
|
|
606
560
|
{{ userInfo.nickname || '未设置' }}
|
|
607
561
|
</div>
|
|
608
562
|
</div>
|
|
609
563
|
|
|
610
564
|
<!-- 邮箱 -->
|
|
611
|
-
<div>
|
|
612
|
-
<label class="
|
|
565
|
+
<div class="settings-form-item">
|
|
566
|
+
<label class="settings-form-label">邮箱</label>
|
|
613
567
|
<el-input
|
|
614
568
|
v-if="isEditingUserInfo"
|
|
615
569
|
v-model="editingUserInfo.email"
|
|
@@ -618,15 +572,15 @@
|
|
|
618
572
|
/>
|
|
619
573
|
<div
|
|
620
574
|
v-else
|
|
621
|
-
class="
|
|
575
|
+
class="settings-form-value"
|
|
622
576
|
>
|
|
623
577
|
{{ userInfo.email || '未设置' }}
|
|
624
578
|
</div>
|
|
625
579
|
</div>
|
|
626
580
|
|
|
627
581
|
<!-- 手机号 -->
|
|
628
|
-
<div>
|
|
629
|
-
<label class="
|
|
582
|
+
<div class="settings-form-item">
|
|
583
|
+
<label class="settings-form-label">手机号</label>
|
|
630
584
|
<el-input
|
|
631
585
|
v-if="isEditingUserInfo"
|
|
632
586
|
v-model="editingUserInfo.phone"
|
|
@@ -635,15 +589,15 @@
|
|
|
635
589
|
/>
|
|
636
590
|
<div
|
|
637
591
|
v-else
|
|
638
|
-
class="
|
|
592
|
+
class="settings-form-value"
|
|
639
593
|
>
|
|
640
594
|
{{ userInfo.phone || '未设置' }}
|
|
641
595
|
</div>
|
|
642
596
|
</div>
|
|
643
597
|
|
|
644
598
|
<!-- 个人简介 -->
|
|
645
|
-
<div>
|
|
646
|
-
<label class="
|
|
599
|
+
<div class="settings-form-item">
|
|
600
|
+
<label class="settings-form-label">个人简介</label>
|
|
647
601
|
<el-input
|
|
648
602
|
v-if="isEditingUserInfo"
|
|
649
603
|
v-model="editingUserInfo.bio"
|
|
@@ -654,14 +608,14 @@
|
|
|
654
608
|
/>
|
|
655
609
|
<div
|
|
656
610
|
v-else
|
|
657
|
-
class="
|
|
611
|
+
class="settings-form-value bio-value"
|
|
658
612
|
>
|
|
659
613
|
{{ userInfo.bio || '这个人很懒,什么都没写~' }}
|
|
660
614
|
</div>
|
|
661
615
|
</div>
|
|
662
616
|
|
|
663
617
|
<!-- 编辑按钮 -->
|
|
664
|
-
<div v-if="isEditingUserInfo" class="
|
|
618
|
+
<div v-if="isEditingUserInfo" class="settings-form-actions">
|
|
665
619
|
<el-button size="default" @click="cancelEditUserInfo">取消</el-button>
|
|
666
620
|
<el-button
|
|
667
621
|
type="primary"
|
|
@@ -685,16 +639,16 @@
|
|
|
685
639
|
<!-- 右键菜单 -->
|
|
686
640
|
<div
|
|
687
641
|
v-if="contextMenu.visible"
|
|
688
|
-
class="context-menu
|
|
642
|
+
class="context-menu"
|
|
689
643
|
:style="{ left: contextMenu.x + 'px', top: contextMenu.y + 'px' }"
|
|
690
644
|
>
|
|
691
|
-
<div class="
|
|
645
|
+
<div class="context-menu-item" @click="handleRemoveChat">删除聊天</div>
|
|
692
646
|
</div>
|
|
693
647
|
</el-dialog>
|
|
694
648
|
</template>
|
|
695
649
|
|
|
696
650
|
<script setup>
|
|
697
|
-
import { computed, watch, nextTick, ref, onMounted, onUnmounted
|
|
651
|
+
import { computed, watch, nextTick, ref, onMounted, onUnmounted } from 'vue'
|
|
698
652
|
import {
|
|
699
653
|
Camera,
|
|
700
654
|
ChatDotRound,
|
|
@@ -711,15 +665,14 @@ import {
|
|
|
711
665
|
Download
|
|
712
666
|
} from '@element-plus/icons-vue'
|
|
713
667
|
import { useChat } from '../composables/useChat.js'
|
|
714
|
-
import AvatarCrop from './AvatarCrop.vue'
|
|
715
668
|
import { ElMessage } from 'element-plus'
|
|
669
|
+
import AvatarCrop from './AvatarCrop.vue'
|
|
716
670
|
|
|
717
671
|
const props = defineProps({
|
|
718
672
|
modelValue: { type: Boolean, default: false },
|
|
719
673
|
config: { type: Object, required: true },
|
|
720
674
|
width: { type: [String, Number], default: '1100px' }
|
|
721
675
|
})
|
|
722
|
-
|
|
723
676
|
const emit = defineEmits(['update:modelValue', 'open', 'close', 'message', 'send', 'error'])
|
|
724
677
|
|
|
725
678
|
const visible = computed({
|
|
@@ -727,7 +680,6 @@ const visible = computed({
|
|
|
727
680
|
set: (val) => emit('update:modelValue', val)
|
|
728
681
|
})
|
|
729
682
|
|
|
730
|
-
// 使用聊天 hook
|
|
731
683
|
const {
|
|
732
684
|
myUsername,
|
|
733
685
|
myAvatar,
|
|
@@ -770,7 +722,6 @@ const {
|
|
|
770
722
|
updateUserInfo
|
|
771
723
|
} = useChat(props.config)
|
|
772
724
|
|
|
773
|
-
// 导航 tab
|
|
774
725
|
const navTabs = computed(() => {
|
|
775
726
|
const tabs = [{ id: 'chat', icon: ChatDotRound, badge: 0 }]
|
|
776
727
|
|
|
@@ -784,47 +735,33 @@ const navTabs = computed(() => {
|
|
|
784
735
|
|
|
785
736
|
return tabs
|
|
786
737
|
})
|
|
787
|
-
|
|
788
738
|
const currentNavTab = ref('chat')
|
|
789
739
|
const currentChatId = ref(null)
|
|
790
740
|
const currentChat = ref(null)
|
|
791
741
|
const currentSelectedFriend = ref(null)
|
|
792
742
|
const showChatDetail = ref(false)
|
|
793
|
-
|
|
794
|
-
// 用户信息编辑
|
|
795
743
|
const isEditingUserInfo = ref(false)
|
|
796
744
|
const editingUserInfo = ref({ nickname: '', email: '', phone: '', bio: '' })
|
|
797
745
|
const savingUserInfo = ref(false)
|
|
798
|
-
|
|
799
|
-
// 设置弹窗
|
|
800
746
|
const showSettingsDialog = ref(false)
|
|
801
|
-
|
|
802
|
-
// 头像相关
|
|
803
747
|
const showAvatarEditor = ref(false)
|
|
804
748
|
const avatarUploading = ref(false)
|
|
805
749
|
const avatarInputRef = ref(null)
|
|
806
750
|
const avatarImageSrc = ref('')
|
|
807
|
-
|
|
808
|
-
// 文件上传相关
|
|
809
751
|
const fileInputRef = ref(null)
|
|
810
752
|
const pendingFiles = ref([])
|
|
811
|
-
|
|
812
|
-
// 右键菜单
|
|
813
753
|
const contextMenu = ref({ visible: false, x: 0, y: 0, chat: null })
|
|
814
754
|
|
|
815
|
-
|
|
816
|
-
const showContextMenu = (e, chat) => {
|
|
755
|
+
const showContextMenuFn = (e, chat) => {
|
|
817
756
|
e.preventDefault()
|
|
818
757
|
e.stopPropagation()
|
|
819
758
|
contextMenu.value = { visible: true, x: e.clientX, y: e.clientY, chat }
|
|
820
759
|
}
|
|
821
760
|
|
|
822
|
-
// 隐藏右键菜单
|
|
823
761
|
const hideContextMenu = () => {
|
|
824
762
|
contextMenu.value.visible = false
|
|
825
763
|
}
|
|
826
764
|
|
|
827
|
-
// 删除聊天
|
|
828
765
|
const handleRemoveChat = async () => {
|
|
829
766
|
if (!contextMenu.value.chat) return
|
|
830
767
|
const success = await setFriendToChatStatus(contextMenu.value.chat.id, 0)
|
|
@@ -837,7 +774,6 @@ const handleRemoveChat = async () => {
|
|
|
837
774
|
hideContextMenu()
|
|
838
775
|
}
|
|
839
776
|
|
|
840
|
-
// 选择聊天
|
|
841
777
|
const selectChat = (chat) => {
|
|
842
778
|
currentChatId.value = chat.id
|
|
843
779
|
currentChat.value = chat
|
|
@@ -851,14 +787,12 @@ const selectChat = (chat) => {
|
|
|
851
787
|
})
|
|
852
788
|
}
|
|
853
789
|
|
|
854
|
-
// 选择好友
|
|
855
790
|
const selectFriend = (friend) => {
|
|
856
791
|
currentSelectedFriend.value = friend
|
|
857
792
|
currentChatId.value = null
|
|
858
793
|
currentChat.value = null
|
|
859
794
|
}
|
|
860
795
|
|
|
861
|
-
// 开始聊天
|
|
862
796
|
const handleStartChat = async () => {
|
|
863
797
|
if (!currentSelectedFriend.value) return
|
|
864
798
|
|
|
@@ -874,17 +808,14 @@ const handleStartChat = async () => {
|
|
|
874
808
|
}
|
|
875
809
|
}
|
|
876
810
|
|
|
877
|
-
// 头像点击
|
|
878
811
|
const handleAvatarClick = () => {
|
|
879
812
|
showSettingsDialog.value = true
|
|
880
813
|
}
|
|
881
814
|
|
|
882
|
-
// 触发头像上传
|
|
883
815
|
const triggerAvatarUpload = () => {
|
|
884
816
|
avatarInputRef.value?.click()
|
|
885
817
|
}
|
|
886
818
|
|
|
887
|
-
// 文件选择
|
|
888
819
|
const triggerFileSelect = () => {
|
|
889
820
|
fileInputRef.value?.click()
|
|
890
821
|
}
|
|
@@ -916,7 +847,6 @@ const handleFileSelect = (e) => {
|
|
|
916
847
|
}
|
|
917
848
|
}
|
|
918
849
|
|
|
919
|
-
// 移除待发送文件
|
|
920
850
|
const removePendingFile = (index) => {
|
|
921
851
|
const file = pendingFiles.value[index]
|
|
922
852
|
if (file.previewUrl) {
|
|
@@ -925,7 +855,6 @@ const removePendingFile = (index) => {
|
|
|
925
855
|
pendingFiles.value.splice(index, 1)
|
|
926
856
|
}
|
|
927
857
|
|
|
928
|
-
// 格式化文件大小
|
|
929
858
|
const formatFileSize = (bytes) => {
|
|
930
859
|
if (bytes === 0) return '0 B'
|
|
931
860
|
const k = 1024
|
|
@@ -934,7 +863,6 @@ const formatFileSize = (bytes) => {
|
|
|
934
863
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
|
935
864
|
}
|
|
936
865
|
|
|
937
|
-
// 发送消息
|
|
938
866
|
const handleSend = async () => {
|
|
939
867
|
if (!inputText.value.trim() && pendingFiles.value.length === 0) return
|
|
940
868
|
|
|
@@ -953,7 +881,6 @@ const handleSend = async () => {
|
|
|
953
881
|
emit('send', { text: textToSend, files: filesToSend })
|
|
954
882
|
}
|
|
955
883
|
|
|
956
|
-
// 处理粘贴
|
|
957
884
|
const handlePaste = (e) => {
|
|
958
885
|
const items = e.clipboardData?.items
|
|
959
886
|
if (!items) return
|
|
@@ -982,7 +909,6 @@ const handlePaste = (e) => {
|
|
|
982
909
|
}
|
|
983
910
|
}
|
|
984
911
|
|
|
985
|
-
// 打开文件
|
|
986
912
|
const openFile = (fileUrl) => {
|
|
987
913
|
if (!fileUrl) {
|
|
988
914
|
ElMessage.warning('文件地址无效')
|
|
@@ -991,12 +917,10 @@ const openFile = (fileUrl) => {
|
|
|
991
917
|
window.open(fileUrl, '_blank')
|
|
992
918
|
}
|
|
993
919
|
|
|
994
|
-
// 图片加载错误
|
|
995
920
|
const handleImageError = (e) => {
|
|
996
921
|
console.warn('图片加载失败', e)
|
|
997
922
|
}
|
|
998
923
|
|
|
999
|
-
// 处理头像文件选择
|
|
1000
924
|
const handleAvatarFileChange = (e) => {
|
|
1001
925
|
const file = e.target.files[0]
|
|
1002
926
|
if (!file) return
|
|
@@ -1019,13 +943,13 @@ const handleAvatarFileChange = (e) => {
|
|
|
1019
943
|
reader.readAsDataURL(file)
|
|
1020
944
|
}
|
|
1021
945
|
|
|
1022
|
-
// 处理头像裁剪确认
|
|
1023
946
|
const handleAvatarCropConfirm = async ({ file }) => {
|
|
1024
947
|
if (!file) return
|
|
1025
948
|
|
|
1026
949
|
avatarUploading.value = true
|
|
1027
950
|
try {
|
|
1028
|
-
const
|
|
951
|
+
const { ChatApi } = await import('../core/api.js')
|
|
952
|
+
const api = new ChatApi(props.config)
|
|
1029
953
|
const res = await api.uploadAvatar(file, myUsername)
|
|
1030
954
|
if (res.code === 200) {
|
|
1031
955
|
ElMessage.success('头像上传成功')
|
|
@@ -1050,7 +974,6 @@ const resetAvatar = () => {
|
|
|
1050
974
|
}
|
|
1051
975
|
}
|
|
1052
976
|
|
|
1053
|
-
// 开始编辑用户信息
|
|
1054
977
|
const startEditUserInfo = () => {
|
|
1055
978
|
editingUserInfo.value = {
|
|
1056
979
|
nickname: userInfo.value.nickname || '',
|
|
@@ -1061,13 +984,11 @@ const startEditUserInfo = () => {
|
|
|
1061
984
|
isEditingUserInfo.value = true
|
|
1062
985
|
}
|
|
1063
986
|
|
|
1064
|
-
// 取消编辑
|
|
1065
987
|
const cancelEditUserInfo = () => {
|
|
1066
988
|
isEditingUserInfo.value = false
|
|
1067
989
|
editingUserInfo.value = { nickname: '', email: '', phone: '', bio: '' }
|
|
1068
990
|
}
|
|
1069
991
|
|
|
1070
|
-
// 保存用户信息
|
|
1071
992
|
const saveUserInfo = async () => {
|
|
1072
993
|
savingUserInfo.value = true
|
|
1073
994
|
try {
|
|
@@ -1086,7 +1007,6 @@ const saveUserInfo = async () => {
|
|
|
1086
1007
|
}
|
|
1087
1008
|
}
|
|
1088
1009
|
|
|
1089
|
-
// 处理弹窗关闭
|
|
1090
1010
|
const handleClosed = () => {
|
|
1091
1011
|
reset()
|
|
1092
1012
|
closeWebSocket()
|
|
@@ -1097,7 +1017,6 @@ const handleClosed = () => {
|
|
|
1097
1017
|
emit('close')
|
|
1098
1018
|
}
|
|
1099
1019
|
|
|
1100
|
-
// 处理弹窗打开
|
|
1101
1020
|
const handleOpen = async () => {
|
|
1102
1021
|
await Promise.all([getFriendList(), loadFriendApplyList(), getUserInfo()])
|
|
1103
1022
|
initWebSocket()
|
|
@@ -1107,7 +1026,6 @@ const handleOpen = async () => {
|
|
|
1107
1026
|
emit('open')
|
|
1108
1027
|
}
|
|
1109
1028
|
|
|
1110
|
-
// 生命周期
|
|
1111
1029
|
onMounted(() => {
|
|
1112
1030
|
document.addEventListener('click', hideContextMenu)
|
|
1113
1031
|
})
|
|
@@ -1134,12 +1052,12 @@ onUnmounted(() => {
|
|
|
1134
1052
|
}
|
|
1135
1053
|
|
|
1136
1054
|
/* 微信样式消息气泡 */
|
|
1137
|
-
.
|
|
1055
|
+
.bubble-self {
|
|
1138
1056
|
position: relative;
|
|
1139
1057
|
background-color: #95ec69 !important;
|
|
1140
1058
|
}
|
|
1141
1059
|
|
|
1142
|
-
.
|
|
1060
|
+
.bubble-self::after {
|
|
1143
1061
|
content: '';
|
|
1144
1062
|
position: absolute;
|
|
1145
1063
|
right: -5px;
|
|
@@ -1151,12 +1069,12 @@ onUnmounted(() => {
|
|
|
1151
1069
|
box-shadow: 2px -2px 2px 0 rgba(0, 0, 0, 0.05);
|
|
1152
1070
|
}
|
|
1153
1071
|
|
|
1154
|
-
.
|
|
1072
|
+
.bubble-other {
|
|
1155
1073
|
position: relative;
|
|
1156
1074
|
background-color: white !important;
|
|
1157
1075
|
}
|
|
1158
1076
|
|
|
1159
|
-
.
|
|
1077
|
+
.bubble-other::after {
|
|
1160
1078
|
content: '';
|
|
1161
1079
|
position: absolute;
|
|
1162
1080
|
left: -5px;
|
|
@@ -1169,14 +1087,14 @@ onUnmounted(() => {
|
|
|
1169
1087
|
}
|
|
1170
1088
|
|
|
1171
1089
|
/* 消息列表滚动条 */
|
|
1172
|
-
.
|
|
1090
|
+
.messages-container::-webkit-scrollbar {
|
|
1173
1091
|
width: 6px;
|
|
1174
1092
|
}
|
|
1175
|
-
.
|
|
1093
|
+
.messages-container::-webkit-scrollbar-thumb {
|
|
1176
1094
|
background: #ccc;
|
|
1177
1095
|
border-radius: 3px;
|
|
1178
1096
|
}
|
|
1179
|
-
.
|
|
1097
|
+
.messages-container::-webkit-scrollbar-track {
|
|
1180
1098
|
background: transparent;
|
|
1181
1099
|
}
|
|
1182
1100
|
|
|
@@ -1197,130 +1115,980 @@ onUnmounted(() => {
|
|
|
1197
1115
|
color: #1f2937;
|
|
1198
1116
|
}
|
|
1199
1117
|
|
|
1200
|
-
/*
|
|
1201
|
-
.
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
.
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
.
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
.
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
.
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
.
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
.
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
.
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
.
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
.
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
.
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
.
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
.
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
.
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
.
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
.
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
.
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
.
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1118
|
+
/* 完整的 CSS 样式 */
|
|
1119
|
+
.chat-container {
|
|
1120
|
+
display: flex;
|
|
1121
|
+
height: 680px;
|
|
1122
|
+
background-color: white;
|
|
1123
|
+
overflow: hidden;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
/* 侧边栏导航 */
|
|
1127
|
+
.sidebar-nav {
|
|
1128
|
+
width: 64px;
|
|
1129
|
+
display: flex;
|
|
1130
|
+
flex-direction: column;
|
|
1131
|
+
align-items: center;
|
|
1132
|
+
gap: 8px;
|
|
1133
|
+
background-color: #f9fafb;
|
|
1134
|
+
border-right: 1px solid #e5e7eb;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
.sidebar-avatar {
|
|
1138
|
+
margin-top: 16px;
|
|
1139
|
+
margin-bottom: 16px;
|
|
1140
|
+
cursor: pointer;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
.avatar-img {
|
|
1144
|
+
width: 40px;
|
|
1145
|
+
height: 40px;
|
|
1146
|
+
border-radius: 50%;
|
|
1147
|
+
border: 2px solid #e5e7eb;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
.nav-item {
|
|
1151
|
+
width: 40px;
|
|
1152
|
+
height: 40px;
|
|
1153
|
+
display: flex;
|
|
1154
|
+
align-items: center;
|
|
1155
|
+
justify-content: center;
|
|
1156
|
+
cursor: pointer;
|
|
1157
|
+
border-radius: 8px;
|
|
1158
|
+
transition: all 0.2s;
|
|
1159
|
+
position: relative;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
.nav-item-active {
|
|
1163
|
+
background-color: #f0fdf4;
|
|
1164
|
+
color: #16a34a;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
.nav-item-inactive {
|
|
1168
|
+
color: #6b7280;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
.nav-item-inactive:hover {
|
|
1172
|
+
background-color: #f3f4f6;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
.nav-badge {
|
|
1176
|
+
position: absolute;
|
|
1177
|
+
top: -4px;
|
|
1178
|
+
right: -4px;
|
|
1179
|
+
width: 16px;
|
|
1180
|
+
height: 16px;
|
|
1181
|
+
background-color: #ef4444;
|
|
1182
|
+
border-radius: 50%;
|
|
1183
|
+
font-size: 10px;
|
|
1184
|
+
color: white;
|
|
1185
|
+
display: flex;
|
|
1186
|
+
align-items: center;
|
|
1187
|
+
justify-content: center;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
.nav-spacer {
|
|
1191
|
+
flex: 1;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
/* 内容面板 */
|
|
1195
|
+
.content-panel {
|
|
1196
|
+
width: 288px;
|
|
1197
|
+
background-color: #f5f5f5;
|
|
1198
|
+
border-right: 1px solid #e5e7eb;
|
|
1199
|
+
display: flex;
|
|
1200
|
+
flex-direction: column;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
.search-bar {
|
|
1204
|
+
padding: 12px;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
.content-scroll {
|
|
1208
|
+
flex: 1;
|
|
1209
|
+
overflow-y: auto;
|
|
1210
|
+
min-height: 0;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
/* 聊天和好友项 */
|
|
1214
|
+
.chat-item {
|
|
1215
|
+
display: flex;
|
|
1216
|
+
align-items: center;
|
|
1217
|
+
padding: 12px;
|
|
1218
|
+
cursor: pointer;
|
|
1219
|
+
transition: background-color 0.2s;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
.chat-item:hover {
|
|
1223
|
+
background-color: #e5e5e5;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
.chat-item-active {
|
|
1227
|
+
background-color: #d6d6d6;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
.friend-avatar-wrapper {
|
|
1231
|
+
position: relative;
|
|
1232
|
+
flex-shrink: 0;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
.friend-avatar {
|
|
1236
|
+
width: 44px;
|
|
1237
|
+
height: 44px;
|
|
1238
|
+
border-radius: 50%;
|
|
1239
|
+
object-fit: cover;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
.online-indicator {
|
|
1243
|
+
position: absolute;
|
|
1244
|
+
bottom: 0;
|
|
1245
|
+
right: 0;
|
|
1246
|
+
width: 12px;
|
|
1247
|
+
height: 12px;
|
|
1248
|
+
background-color: #22c55e;
|
|
1249
|
+
border-radius: 50%;
|
|
1250
|
+
border: 2px solid white;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
.online-indicator.offline {
|
|
1254
|
+
background-color: #9ca3af;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
.friend-info {
|
|
1258
|
+
margin-left: 12px;
|
|
1259
|
+
flex: 1;
|
|
1260
|
+
overflow: hidden;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
.friend-header {
|
|
1264
|
+
display: flex;
|
|
1265
|
+
justify-content: space-between;
|
|
1266
|
+
align-items: center;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
.friend-name {
|
|
1270
|
+
font-weight: 500;
|
|
1271
|
+
color: #1f2937;
|
|
1272
|
+
font-size: 14px;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
.last-time {
|
|
1276
|
+
font-size: 12px;
|
|
1277
|
+
color: #9ca3af;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
.friend-preview {
|
|
1281
|
+
display: flex;
|
|
1282
|
+
justify-content: space-between;
|
|
1283
|
+
align-items: center;
|
|
1284
|
+
margin-top: 4px;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
.last-msg {
|
|
1288
|
+
font-size: 12px;
|
|
1289
|
+
color: #6b7280;
|
|
1290
|
+
overflow: hidden;
|
|
1291
|
+
text-overflow: ellipsis;
|
|
1292
|
+
white-space: nowrap;
|
|
1293
|
+
padding-right: 8px;
|
|
1294
|
+
flex: 1;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
.unread-badge {
|
|
1298
|
+
background-color: #ef4444;
|
|
1299
|
+
color: white;
|
|
1300
|
+
font-size: 10px;
|
|
1301
|
+
border-radius: 9999px;
|
|
1302
|
+
padding: 2px 6px;
|
|
1303
|
+
min-width: 18px;
|
|
1304
|
+
text-align: center;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
/* 添加好友 */
|
|
1308
|
+
.add-friend-section {
|
|
1309
|
+
padding: 12px;
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
.add-friend-btn {
|
|
1313
|
+
display: flex;
|
|
1314
|
+
align-items: center;
|
|
1315
|
+
gap: 8px;
|
|
1316
|
+
padding: 8px;
|
|
1317
|
+
border-radius: 8px;
|
|
1318
|
+
cursor: pointer;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
.add-friend-btn:hover {
|
|
1322
|
+
background-color: #e5e5e5;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
.add-friend-icon {
|
|
1326
|
+
width: 44px;
|
|
1327
|
+
height: 44px;
|
|
1328
|
+
background-color: #22c55e;
|
|
1329
|
+
border-radius: 8px;
|
|
1330
|
+
display: flex;
|
|
1331
|
+
align-items: center;
|
|
1332
|
+
justify-content: center;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
.add-friend-text {
|
|
1336
|
+
font-size: 14px;
|
|
1337
|
+
color: #1f2937;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
/* 好友申请 */
|
|
1341
|
+
.friend-request-item {
|
|
1342
|
+
display: flex;
|
|
1343
|
+
align-items: center;
|
|
1344
|
+
justify-content: space-between;
|
|
1345
|
+
padding: 12px;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
.friend-request-item:hover {
|
|
1349
|
+
background-color: #e5e5e5;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
.request-info {
|
|
1353
|
+
display: flex;
|
|
1354
|
+
align-items: center;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
.request-avatar {
|
|
1358
|
+
width: 44px;
|
|
1359
|
+
height: 44px;
|
|
1360
|
+
border-radius: 50%;
|
|
1361
|
+
object-fit: cover;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
.request-details {
|
|
1365
|
+
margin-left: 12px;
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
.request-username {
|
|
1369
|
+
font-weight: 500;
|
|
1370
|
+
color: #1f2937;
|
|
1371
|
+
font-size: 14px;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
.request-desc {
|
|
1375
|
+
font-size: 12px;
|
|
1376
|
+
color: #6b7280;
|
|
1377
|
+
margin-top: 4px;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
/* 聊天区域 */
|
|
1381
|
+
.chat-area {
|
|
1382
|
+
flex: 1;
|
|
1383
|
+
display: flex;
|
|
1384
|
+
flex-direction: column;
|
|
1385
|
+
min-width: 0;
|
|
1386
|
+
background-color: white;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
.friend-profile {
|
|
1390
|
+
flex: 1;
|
|
1391
|
+
display: flex;
|
|
1392
|
+
flex-direction: column;
|
|
1393
|
+
align-items: center;
|
|
1394
|
+
justify-content: center;
|
|
1395
|
+
padding: 32px;
|
|
1396
|
+
background-color: #f5f5f5;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
.profile-avatar {
|
|
1400
|
+
width: 96px;
|
|
1401
|
+
height: 96px;
|
|
1402
|
+
border-radius: 50%;
|
|
1403
|
+
object-fit: cover;
|
|
1404
|
+
margin-bottom: 24px;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
.profile-name {
|
|
1408
|
+
font-size: 20px;
|
|
1409
|
+
font-weight: 500;
|
|
1410
|
+
color: #1f2937;
|
|
1411
|
+
margin-bottom: 8px;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
.profile-status {
|
|
1415
|
+
display: flex;
|
|
1416
|
+
align-items: center;
|
|
1417
|
+
gap: 8px;
|
|
1418
|
+
margin-bottom: 32px;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
.status-dot {
|
|
1422
|
+
width: 8px;
|
|
1423
|
+
height: 8px;
|
|
1424
|
+
border-radius: 50%;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
.status-online {
|
|
1428
|
+
background-color: #22c55e;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
.status-offline {
|
|
1432
|
+
background-color: #9ca3af;
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
.start-chat-btn {
|
|
1436
|
+
width: 160px;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
/* 聊天窗口 */
|
|
1440
|
+
.chat-window {
|
|
1441
|
+
flex: 1;
|
|
1442
|
+
display: flex;
|
|
1443
|
+
flex-direction: column;
|
|
1444
|
+
min-height: 0;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
.chat-header {
|
|
1448
|
+
height: 56px;
|
|
1449
|
+
border-bottom: 1px solid #e5e7eb;
|
|
1450
|
+
display: flex;
|
|
1451
|
+
align-items: center;
|
|
1452
|
+
justify-content: space-between;
|
|
1453
|
+
padding: 0 16px;
|
|
1454
|
+
background-color: white;
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
.chat-title {
|
|
1458
|
+
display: flex;
|
|
1459
|
+
align-items: center;
|
|
1460
|
+
gap: 12px;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
.chat-name {
|
|
1464
|
+
font-weight: 500;
|
|
1465
|
+
color: #1f2937;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
.status-badge {
|
|
1469
|
+
font-size: 12px;
|
|
1470
|
+
padding: 2px 8px;
|
|
1471
|
+
border-radius: 4px;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
.status-badge-online {
|
|
1475
|
+
background-color: #dcfce7;
|
|
1476
|
+
color: #16a34a;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
.status-badge-offline {
|
|
1480
|
+
background-color: #f3f4f6;
|
|
1481
|
+
color: #6b7280;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
.chat-actions {
|
|
1485
|
+
display: flex;
|
|
1486
|
+
align-items: center;
|
|
1487
|
+
gap: 12px;
|
|
1488
|
+
color: #6b7280;
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
.action-icon {
|
|
1492
|
+
cursor: pointer;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
.action-icon:hover {
|
|
1496
|
+
color: #374151;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
.messages-container {
|
|
1500
|
+
flex: 1;
|
|
1501
|
+
overflow-y: auto;
|
|
1502
|
+
padding: 16px;
|
|
1503
|
+
background-color: #f5f5f5;
|
|
1504
|
+
min-height: 0;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
/* 消息样式 */
|
|
1508
|
+
.message-wrapper {
|
|
1509
|
+
display: flex;
|
|
1510
|
+
margin-bottom: 24px;
|
|
1511
|
+
align-items: flex-start;
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
.message-self {
|
|
1515
|
+
flex-direction: row-reverse;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
.message-other {
|
|
1519
|
+
flex-direction: row;
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
.message-avatar {
|
|
1523
|
+
flex-shrink: 0;
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
.avatar-sm {
|
|
1527
|
+
width: 40px;
|
|
1528
|
+
height: 40px;
|
|
1529
|
+
border-radius: 8px;
|
|
1530
|
+
object-fit: cover;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
.message-content-wrapper {
|
|
1534
|
+
display: flex;
|
|
1535
|
+
flex-direction: column;
|
|
1536
|
+
max-width: 75%;
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
.content-self {
|
|
1540
|
+
margin-right: 12px;
|
|
1541
|
+
align-items: flex-end;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
.content-other {
|
|
1545
|
+
margin-left: 12px;
|
|
1546
|
+
align-items: flex-start;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
.sender-name {
|
|
1550
|
+
font-size: 12px;
|
|
1551
|
+
color: #6b7280;
|
|
1552
|
+
margin-bottom: 4px;
|
|
1553
|
+
margin-left: 4px;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
.message-bubble-wrapper {
|
|
1557
|
+
position: relative;
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
.message-bubble {
|
|
1561
|
+
padding: 8px 12px;
|
|
1562
|
+
font-size: 14px;
|
|
1563
|
+
word-break: break-all;
|
|
1564
|
+
white-space: pre-wrap;
|
|
1565
|
+
border-radius: 8px;
|
|
1566
|
+
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
.image-bubble {
|
|
1570
|
+
border-radius: 8px;
|
|
1571
|
+
position: relative;
|
|
1572
|
+
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
1573
|
+
cursor: pointer;
|
|
1574
|
+
overflow: hidden;
|
|
1575
|
+
max-width: 300px;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
.message-image {
|
|
1579
|
+
width: 100%;
|
|
1580
|
+
height: auto;
|
|
1581
|
+
display: block;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
.image-size {
|
|
1585
|
+
position: absolute;
|
|
1586
|
+
left: 4px;
|
|
1587
|
+
bottom: 0;
|
|
1588
|
+
color: white;
|
|
1589
|
+
font-size: 10px;
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
.file-bubble {
|
|
1593
|
+
border-radius: 8px;
|
|
1594
|
+
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
1595
|
+
cursor: pointer;
|
|
1596
|
+
overflow: hidden;
|
|
1597
|
+
min-width: 200px;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
.file-content {
|
|
1601
|
+
display: flex;
|
|
1602
|
+
align-items: center;
|
|
1603
|
+
gap: 12px;
|
|
1604
|
+
padding: 12px 16px;
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
.file-icon {
|
|
1608
|
+
width: 40px;
|
|
1609
|
+
height: 40px;
|
|
1610
|
+
display: flex;
|
|
1611
|
+
align-items: center;
|
|
1612
|
+
justify-content: center;
|
|
1613
|
+
border-radius: 8px;
|
|
1614
|
+
flex-shrink: 0;
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
.bubble-self .file-icon {
|
|
1618
|
+
color: #374151;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
.bubble-other .file-icon {
|
|
1622
|
+
color: #6b7280;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
.file-info {
|
|
1626
|
+
flex: 1;
|
|
1627
|
+
min-width: 0;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
.file-name {
|
|
1631
|
+
overflow: hidden;
|
|
1632
|
+
text-overflow: ellipsis;
|
|
1633
|
+
font-size: 14px;
|
|
1634
|
+
font-weight: 500;
|
|
1635
|
+
line-height: 1.2;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
.bubble-self .file-name {
|
|
1639
|
+
color: #1f2937;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
.bubble-other .file-name {
|
|
1643
|
+
color: #1f2937;
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
.file-meta {
|
|
1647
|
+
font-size: 12px;
|
|
1648
|
+
margin-top: 4px;
|
|
1649
|
+
display: flex;
|
|
1650
|
+
align-items: center;
|
|
1651
|
+
gap: 8px;
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
.bubble-self .file-meta {
|
|
1655
|
+
color: #4b5563;
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
.bubble-other .file-meta {
|
|
1659
|
+
color: #6b7280;
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
.message-time {
|
|
1663
|
+
font-size: 10px;
|
|
1664
|
+
color: #9ca3af;
|
|
1665
|
+
margin-top: 4px;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
.time-right {
|
|
1669
|
+
text-align: right;
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
.time-left {
|
|
1673
|
+
text-align: left;
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
/* 输入区域 */
|
|
1677
|
+
.input-area {
|
|
1678
|
+
background-color: white;
|
|
1679
|
+
border-top: 1px solid #e5e7eb;
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
.pending-files {
|
|
1683
|
+
padding: 8px 12px;
|
|
1684
|
+
border-bottom: 1px solid #f3f4f6;
|
|
1685
|
+
display: flex;
|
|
1686
|
+
flex-wrap: wrap;
|
|
1687
|
+
gap: 8px;
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
.pending-file {
|
|
1691
|
+
position: relative;
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
.pending-image-wrapper {
|
|
1695
|
+
position: relative;
|
|
1696
|
+
width: 80px;
|
|
1697
|
+
height: 80px;
|
|
1698
|
+
border-radius: 8px;
|
|
1699
|
+
overflow: hidden;
|
|
1700
|
+
border: 1px solid #e5e7eb;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
.pending-image {
|
|
1704
|
+
width: 100%;
|
|
1705
|
+
height: 100%;
|
|
1706
|
+
object-fit: cover;
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
.pending-file-wrapper {
|
|
1710
|
+
position: relative;
|
|
1711
|
+
width: 96px;
|
|
1712
|
+
height: 80px;
|
|
1713
|
+
border-radius: 8px;
|
|
1714
|
+
border: 1px solid #e5e7eb;
|
|
1715
|
+
background-color: #f9fafb;
|
|
1716
|
+
display: flex;
|
|
1717
|
+
flex-direction: column;
|
|
1718
|
+
align-items: center;
|
|
1719
|
+
justify-content: center;
|
|
1720
|
+
padding: 4px;
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
.pending-file-icon {
|
|
1724
|
+
color: #9ca3af;
|
|
1725
|
+
font-size: 28px;
|
|
1726
|
+
margin-bottom: 4px;
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
.pending-file-name {
|
|
1730
|
+
font-size: 10px;
|
|
1731
|
+
color: #6b7280;
|
|
1732
|
+
overflow: hidden;
|
|
1733
|
+
text-overflow: ellipsis;
|
|
1734
|
+
white-space: nowrap;
|
|
1735
|
+
width: 100%;
|
|
1736
|
+
text-align: center;
|
|
1737
|
+
padding: 0 4px;
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
.remove-file-btn {
|
|
1741
|
+
position: absolute;
|
|
1742
|
+
top: 4px;
|
|
1743
|
+
right: 4px;
|
|
1744
|
+
width: 20px;
|
|
1745
|
+
height: 20px;
|
|
1746
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
1747
|
+
color: white;
|
|
1748
|
+
border-radius: 50%;
|
|
1749
|
+
display: flex;
|
|
1750
|
+
align-items: center;
|
|
1751
|
+
justify-content: center;
|
|
1752
|
+
cursor: pointer;
|
|
1753
|
+
transition: background-color 0.2s;
|
|
1754
|
+
font-size: 14px;
|
|
1755
|
+
border: none;
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
.remove-file-btn:hover {
|
|
1759
|
+
background-color: rgba(0, 0, 0, 0.7);
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
.input-actions {
|
|
1763
|
+
display: flex;
|
|
1764
|
+
align-items: center;
|
|
1765
|
+
padding: 12px;
|
|
1766
|
+
gap: 8px;
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
.input-wrapper {
|
|
1770
|
+
padding: 0 12px 12px;
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
.message-input {
|
|
1774
|
+
width: 100%;
|
|
1775
|
+
resize: none;
|
|
1776
|
+
border: none;
|
|
1777
|
+
outline: none;
|
|
1778
|
+
font-size: 14px;
|
|
1779
|
+
height: 80px;
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
.send-btn-wrapper {
|
|
1783
|
+
display: flex;
|
|
1784
|
+
justify-content: flex-end;
|
|
1785
|
+
padding: 0 12px 12px;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
.send-btn {
|
|
1789
|
+
background-color: #07c160;
|
|
1790
|
+
border: none;
|
|
1791
|
+
font-size: 14px;
|
|
1792
|
+
padding: 8px 24px;
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
.send-btn:hover {
|
|
1796
|
+
background-color: #06ad56;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
.hidden-file-input {
|
|
1800
|
+
display: none;
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
/* 空状态 */
|
|
1804
|
+
.empty-state {
|
|
1805
|
+
flex: 1;
|
|
1806
|
+
display: flex;
|
|
1807
|
+
align-items: center;
|
|
1808
|
+
justify-content: center;
|
|
1809
|
+
flex-direction: column;
|
|
1810
|
+
background-color: #f5f5f5;
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
.empty-icon {
|
|
1814
|
+
color: #d1d5db;
|
|
1815
|
+
margin-bottom: 8px;
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
.empty-text {
|
|
1819
|
+
color: #9ca3af;
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
/* 详情面板 */
|
|
1823
|
+
.detail-panel {
|
|
1824
|
+
width: 256px;
|
|
1825
|
+
background-color: #f5f5f5;
|
|
1826
|
+
border-left: 1px solid #e5e7eb;
|
|
1827
|
+
display: flex;
|
|
1828
|
+
flex-direction: column;
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
.detail-header {
|
|
1832
|
+
height: 56px;
|
|
1833
|
+
display: flex;
|
|
1834
|
+
align-items: center;
|
|
1835
|
+
justify-content: center;
|
|
1836
|
+
border-bottom: 1px solid #e5e7eb;
|
|
1837
|
+
font-weight: 500;
|
|
1838
|
+
color: #374151;
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
.detail-content {
|
|
1842
|
+
flex: 1;
|
|
1843
|
+
padding: 16px;
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
.detail-profile {
|
|
1847
|
+
display: flex;
|
|
1848
|
+
flex-direction: column;
|
|
1849
|
+
align-items: center;
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
.detail-avatar {
|
|
1853
|
+
width: 80px;
|
|
1854
|
+
height: 80px;
|
|
1855
|
+
border-radius: 50%;
|
|
1856
|
+
object-fit: cover;
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
.detail-name {
|
|
1860
|
+
margin-top: 12px;
|
|
1861
|
+
font-weight: 500;
|
|
1862
|
+
color: #1f2937;
|
|
1863
|
+
font-size: 18px;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
.detail-actions {
|
|
1867
|
+
margin-top: 24px;
|
|
1868
|
+
width: 100%;
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
.detail-action-item {
|
|
1872
|
+
padding: 12px;
|
|
1873
|
+
border-bottom: 1px solid #f3f4f6;
|
|
1874
|
+
cursor: pointer;
|
|
1875
|
+
background-color: white;
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
.detail-action-item:hover {
|
|
1879
|
+
background-color: #f9fafb;
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
.detail-action-item:first-child {
|
|
1883
|
+
border-radius: 8px 8px 0 0;
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
.detail-action-item:last-child {
|
|
1887
|
+
border-bottom: none;
|
|
1888
|
+
border-radius: 0 0 8px 8px;
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
/* 搜索用户 */
|
|
1892
|
+
.search-users-wrapper {
|
|
1893
|
+
margin-bottom: 16px;
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
.search-users-input {
|
|
1897
|
+
position: relative;
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
.users-list-scroll {
|
|
1901
|
+
max-height: 400px;
|
|
1902
|
+
overflow-y: auto;
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
.available-user-item {
|
|
1906
|
+
display: flex;
|
|
1907
|
+
align-items: center;
|
|
1908
|
+
justify-content: space-between;
|
|
1909
|
+
padding: 12px;
|
|
1910
|
+
margin-bottom: 8px;
|
|
1911
|
+
border-radius: 8px;
|
|
1912
|
+
transition: background-color 0.2s;
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
.available-user-item:hover {
|
|
1916
|
+
background-color: #f9fafb;
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
.available-user-info {
|
|
1920
|
+
display: flex;
|
|
1921
|
+
align-items: center;
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
.available-user-avatar {
|
|
1925
|
+
width: 40px;
|
|
1926
|
+
height: 40px;
|
|
1927
|
+
border-radius: 50%;
|
|
1928
|
+
object-fit: cover;
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
.available-user-name {
|
|
1932
|
+
margin-left: 12px;
|
|
1933
|
+
font-weight: 500;
|
|
1934
|
+
color: #1f2937;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
/* 设置页面 */
|
|
1938
|
+
.settings-container {
|
|
1939
|
+
display: flex;
|
|
1940
|
+
flex-direction: column;
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
.settings-avatar-section {
|
|
1944
|
+
display: flex;
|
|
1945
|
+
flex-direction: column;
|
|
1946
|
+
align-items: center;
|
|
1947
|
+
margin-bottom: 32px;
|
|
1948
|
+
padding-bottom: 24px;
|
|
1949
|
+
border-bottom: 1px solid #f3f4f6;
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
.settings-avatar-wrapper {
|
|
1953
|
+
position: relative;
|
|
1954
|
+
margin-bottom: 16px;
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
.settings-avatar {
|
|
1958
|
+
width: 112px;
|
|
1959
|
+
height: 112px;
|
|
1960
|
+
border-radius: 50%;
|
|
1961
|
+
object-fit: cover;
|
|
1962
|
+
border: 4px solid white;
|
|
1963
|
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
.settings-avatar-edit {
|
|
1967
|
+
position: absolute;
|
|
1968
|
+
bottom: -4px;
|
|
1969
|
+
right: -4px;
|
|
1970
|
+
width: 40px;
|
|
1971
|
+
height: 40px;
|
|
1972
|
+
background-color: #22c55e;
|
|
1973
|
+
border-radius: 50%;
|
|
1974
|
+
display: flex;
|
|
1975
|
+
align-items: center;
|
|
1976
|
+
justify-content: center;
|
|
1977
|
+
cursor: pointer;
|
|
1978
|
+
transition: background-color 0.2s;
|
|
1979
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
.settings-avatar-edit:hover {
|
|
1983
|
+
background-color: #16a34a;
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
.settings-avatar-icon {
|
|
1987
|
+
color: white;
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
.hidden-avatar-input {
|
|
1991
|
+
display: none;
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
.settings-user-display {
|
|
1995
|
+
text-align: center;
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
.settings-nickname {
|
|
1999
|
+
font-weight: 600;
|
|
2000
|
+
color: #1f2937;
|
|
2001
|
+
font-size: 20px;
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
.settings-username {
|
|
2005
|
+
font-size: 14px;
|
|
2006
|
+
color: #6b7280;
|
|
2007
|
+
margin-top: 4px;
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
.settings-form-section {
|
|
2011
|
+
gap: 20px;
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
.settings-form-header {
|
|
2015
|
+
display: flex;
|
|
2016
|
+
align-items: center;
|
|
2017
|
+
justify-content: space-between;
|
|
2018
|
+
margin-bottom: 8px;
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
.settings-form-title {
|
|
2022
|
+
color: #374151;
|
|
2023
|
+
font-weight: 600;
|
|
2024
|
+
display: flex;
|
|
2025
|
+
align-items: center;
|
|
2026
|
+
gap: 8px;
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
.settings-edit-btn {
|
|
2030
|
+
border-radius: 9999px;
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
.settings-form {
|
|
2034
|
+
background-color: #f9fafb;
|
|
2035
|
+
border-radius: 16px;
|
|
2036
|
+
padding: 24px;
|
|
2037
|
+
gap: 20px;
|
|
2038
|
+
display: flex;
|
|
2039
|
+
flex-direction: column;
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
.settings-form-item {
|
|
2043
|
+
display: flex;
|
|
2044
|
+
flex-direction: column;
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
.settings-form-label {
|
|
2048
|
+
display: block;
|
|
2049
|
+
font-size: 14px;
|
|
2050
|
+
color: #4b5563;
|
|
2051
|
+
margin-bottom: 8px;
|
|
2052
|
+
font-weight: 500;
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
.settings-form-value {
|
|
2056
|
+
color: #1f2937;
|
|
2057
|
+
background-color: white;
|
|
2058
|
+
border-radius: 8px;
|
|
2059
|
+
padding: 12px 16px;
|
|
2060
|
+
border: 1px solid #e5e7eb;
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
.bio-value {
|
|
2064
|
+
min-height: 80px;
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
.settings-form-actions {
|
|
2068
|
+
display: flex;
|
|
2069
|
+
gap: 12px;
|
|
2070
|
+
justify-content: flex-end;
|
|
2071
|
+
padding-top: 8px;
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
/* 右键菜单 */
|
|
2075
|
+
.context-menu {
|
|
2076
|
+
position: fixed;
|
|
2077
|
+
background-color: white;
|
|
2078
|
+
border-radius: 8px;
|
|
2079
|
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
2080
|
+
border: 1px solid #e5e7eb;
|
|
2081
|
+
padding: 4px 0;
|
|
2082
|
+
z-index: 1000;
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
.context-menu-item {
|
|
2086
|
+
padding: 8px 16px;
|
|
2087
|
+
cursor: pointer;
|
|
2088
|
+
font-size: 14px;
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
.context-menu-item:hover {
|
|
2092
|
+
background-color: #f3f4f6;
|
|
2093
|
+
}
|
|
1326
2094
|
</style>
|