vue-chat-kit 0.1.2 → 0.2.1

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.
@@ -8,38 +8,35 @@
8
8
  @closed="handleClosed"
9
9
  @open="handleOpen"
10
10
  >
11
- <div class="chat-container flex h-[680px] bg-white overflow-hidden">
11
+ <div class="chat-container">
12
12
  <!-- 左侧图标导航栏 -->
13
- <div class="w-16 theme-white flex flex-col items-center gap-2 bg-gray-50 border-r">
14
- <div class="mb-4 cursor-pointer mt-4" @click="handleAvatarClick">
15
- <img :src="myAvatar" alt="头像" class="w-10 h-10 rounded-full border-2 border-gray-200" />
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
- 'w-10 h-10 flex items-center justify-center cursor-pointer rounded-lg transition-all relative',
23
- currentNavTab === tab.id ? 'bg-green-50 text-green-600' : 'text-gray-500 hover:bg-gray-100'
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="flex-1"></div>
35
+ <div class="nav-spacer"></div>
39
36
 
40
37
  <div
41
38
  v-if="config.modules.settings"
42
- class="w-10 h-10 flex items-center justify-center cursor-pointer rounded-lg hover:bg-gray-100 transition-all mb-4 text-gray-500"
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="w-72 bg-[#f5f5f5] border-r border-gray-200 flex flex-col">
48
+ <div class="content-panel">
52
49
  <!-- 搜索栏 -->
53
- <div class="p-3">
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="flex-1 overflow-y-auto min-h-0">
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
- 'flex items-center p-3 cursor-pointer hover:bg-[#e5e5e5] transition-colors',
70
- currentChatId === chat.id ? 'bg-[#d6d6d6]' : ''
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="relative flex-shrink-0">
72
+ <div class="friend-avatar-wrapper">
76
73
  <img
77
74
  :src="chat.avatar"
78
75
  :alt="chat.name"
79
- class="w-11 h-11 rounded-full object-cover"
76
+ class="friend-avatar"
80
77
  />
81
78
  <span
82
79
  v-if="chat.online"
83
- class="absolute bottom-0 right-0 w-3 h-3 bg-green-500 rounded-full border-2 border-white"
80
+ class="online-indicator"
84
81
  ></span>
85
82
  </div>
86
- <div class="ml-3 flex-1 overflow-hidden">
87
- <div class="flex justify-between items-center">
88
- <span class="font-medium text-gray-800 text-sm">{{ chat.name }}</span>
89
- <span class="text-xs text-gray-400">{{ formatLastTime(chat.lastTime) }}</span>
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="flex justify-between items-center mt-1">
92
- <span class="text-xs text-gray-500 truncate pr-2">{{ chat.lastMsg }}</span>
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="bg-red-500 text-white text-xs rounded-full px-1.5 py-0.5 min-w-[18px] text-center"
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="p-3">
103
+ <div class="add-friend-section">
107
104
  <div
108
- class="flex items-center gap-2 p-2 rounded-lg cursor-pointer hover:bg-[#e5e5e5]"
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="text-sm text-gray-800">添加好友</span>
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="flex items-center p-3 cursor-pointer hover:bg-[#e5e5e5] transition-colors"
117
+ class="chat-item"
123
118
  @click="selectFriend(friend)"
124
119
  >
125
- <div class="relative flex-shrink-0">
120
+ <div class="friend-avatar-wrapper">
126
121
  <img
127
122
  :src="friend.avatar"
128
123
  :alt="friend.name"
129
- class="w-11 h-11 rounded-full object-cover"
124
+ class="friend-avatar"
130
125
  />
131
126
  <span
132
127
  :class="[
133
- 'absolute bottom-0 right-0 w-3 h-3 rounded-full border-2 border-white',
134
- friend.online ? 'bg-green-500' : 'bg-gray-400'
128
+ 'online-indicator',
129
+ friend.online ? 'online' : 'offline'
135
130
  ]"
136
131
  ></span>
137
132
  </div>
138
- <div class="ml-3">
139
- <span class="font-medium text-gray-800 text-sm">{{ friend.name }}</span>
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="flex items-center justify-between p-3 hover:bg-[#e5e5e5]"
150
+ class="friend-request-item"
156
151
  >
157
- <div class="flex items-center">
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="w-11 h-11 rounded-full object-cover"
156
+ class="request-avatar"
162
157
  />
163
- <div class="ml-3">
164
- <div class="font-medium text-gray-800 text-sm">{{ apply.applyUser }}</div>
165
- <div class="text-xs text-gray-500 mt-1">请求添加你为好友</div>
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="flex-1 flex flex-col min-w-0 bg-white">
174
+ <div class="chat-area">
180
175
  <!-- 好友信息展示区域 -->
181
- <div v-if="currentSelectedFriend && !currentChat" class="flex-1 flex flex-col min-h-0">
182
- <div
183
- class="flex-1 flex flex-col items-center justify-center p-8 bg-[#f5f5f5]"
184
- >
185
- <img
186
- :src="currentSelectedFriend.avatar"
187
- :alt="currentSelectedFriend.name"
188
- class="w-24 h-24 rounded-full object-cover mb-6"
189
- />
190
- <div class="text-xl font-medium text-gray-800 mb-2">{{ currentSelectedFriend.name }}</div>
191
- <div class="flex items-center gap-2 mb-8">
192
- <span
193
- :class="[
194
- 'w-2 h-2 rounded-full',
195
- currentSelectedFriend.online ? 'bg-green-500' : 'bg-gray-400'
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="flex-1 flex flex-col min-h-0">
204
+ <div v-if="currentChat" class="chat-window">
214
205
  <!-- 顶部标题栏 -->
215
- <div class="h-14 border-b border-gray-200 flex items-center justify-between px-4 bg-white">
216
- <div class="flex items-center gap-3">
217
- <span class="font-medium text-gray-800">{{ currentChat.name }}</span>
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
- 'text-xs px-2 py-0.5 rounded',
221
- currentChat.online ? 'bg-green-100 text-green-600' : 'bg-gray-100 text-gray-500'
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="flex items-center gap-3 text-gray-500">
228
- <el-icon class="cursor-pointer hover:text-gray-700"><Search /></el-icon>
218
+ <div class="chat-actions">
219
+ <el-icon class="action-icon"><Search /></el-icon>
229
220
  <el-icon
230
- class="cursor-pointer hover:text-gray-700"
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="flex-1 overflow-y-auto p-4 bg-[#f5f5f5] min-h-0 message-list"
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
- 'flex mb-6 items-start',
246
- msg.isSelf ? 'flex-row-reverse' : 'flex-row'
236
+ 'message-wrapper',
237
+ msg.isSelf ? 'message-self' : 'message-other'
247
238
  ]"
248
239
  >
249
240
  <!-- 头像 -->
250
- <div class="flex-shrink-0">
241
+ <div class="message-avatar">
251
242
  <img
252
243
  :src="msg.isSelf ? myAvatar : currentChat.avatar"
253
- class="w-10 h-10 rounded-lg object-cover"
244
+ class="avatar-sm"
254
245
  />
255
246
  </div>
256
247
 
257
248
  <!-- 消息内容 -->
258
249
  <div
259
250
  :class="[
260
- 'flex flex-col max-w-[75%]',
261
- msg.isSelf ? 'mr-3 items-end' : 'ml-3 items-start'
251
+ 'message-content-wrapper',
252
+ msg.isSelf ? 'content-self' : 'content-other'
262
253
  ]"
263
254
  >
264
- <div v-if="!msg.isSelf" class="text-xs text-gray-500 mb-1 ml-1">{{ currentChat.name }}</div>
255
+ <div v-if="!msg.isSelf" class="sender-name">{{ currentChat.name }}</div>
265
256
 
266
- <div class="relative group">
257
+ <div class="message-bubble-wrapper">
267
258
  <!-- 文本消息 -->
268
259
  <div
269
260
  v-if="msg.type === 'text'"
270
261
  :class="[
271
- 'px-3 py-2 text-sm break-all whitespace-pre-wrap rounded-lg shadow-sm',
272
- msg.isSelf ? 'bg-[#95ec69] text-gray-800 self-end message-bubble-self' : 'bg-white text-gray-800 message-bubble-other'
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
- 'rounded-lg relative shadow-sm cursor-pointer overflow-hidden max-w-[300px]',
283
- msg.isSelf ? 'self-end' : 'self-start'
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="w-full h-auto block"
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
- 'rounded-lg shadow-sm cursor-pointer overflow-hidden min-w-[200px]',
308
- msg.isSelf ? 'self-end message-bubble-self' : 'message-bubble-other'
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
- :class="[
314
- 'flex items-center gap-3 px-4 py-3',
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
- <div class="flex-1 min-w-0">
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
- 'text-[10px] text-gray-400 mt-1',
353
- msg.isSelf ? 'text-right' : 'text-left'
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="bg-white border-t border-gray-200">
328
+ <div class="input-area">
365
329
  <!-- 待发送文件预览 -->
366
330
  <div
367
331
  v-if="pendingFiles.length > 0"
368
- class="px-3 py-2 border-b border-gray-100 flex flex-wrap gap-2"
332
+ class="pending-files"
369
333
  >
370
334
  <div
371
335
  v-for="(file, index) in pendingFiles"
372
336
  :key="file.id"
373
- class="relative group"
337
+ class="pending-file"
374
338
  >
375
339
  <!-- 图片预览 -->
376
340
  <div
377
341
  v-if="file.isImage"
378
- class="relative w-20 h-20 rounded-lg overflow-hidden border border-gray-200"
342
+ class="pending-image-wrapper"
379
343
  >
380
344
  <img
381
345
  :src="file.previewUrl"
382
346
  :alt="file.name"
383
- class="w-full h-full object-cover"
347
+ class="pending-image"
384
348
  />
385
349
  <button
386
350
  @click="removePendingFile(index)"
387
- class="absolute top-1 right-1 w-5 h-5 bg-black/50 text-white rounded-full flex items-center justify-center hover:bg-black/70 transition-colors text-xs"
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="relative w-24 h-20 rounded-lg border border-gray-200 bg-gray-50 flex flex-col items-center justify-center p-1"
359
+ class="pending-file-wrapper"
396
360
  >
397
- <el-icon class="text-gray-400 text-2xl mb-1"><Folder /></el-icon>
398
- <span class="text-xs text-gray-500 truncate w-full text-center px-1">{{ file.name }}</span>
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="absolute top-1 right-1 w-5 h-5 bg-black/50 text-white rounded-full flex items-center justify-center hover:bg-black/70 transition-colors text-xs"
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="flex items-center p-3 gap-2">
410
- <el-icon class="text-gray-500 cursor-pointer hover:text-gray-700"><ChatDotRound /></el-icon>
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="text-gray-500 cursor-pointer hover:text-gray-700"
376
+ class="action-icon"
413
377
  @click="triggerFileSelect"
414
378
  ><Folder /></el-icon>
415
- <el-icon class="text-gray-500 cursor-pointer hover:text-gray-700"><Picture /></el-icon>
379
+ <el-icon class="action-icon"><Picture /></el-icon>
416
380
  </div>
417
- <div class="px-3 pb-3">
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="w-full resize-none border-0 outline-none text-sm h-[80px]"
387
+ class="message-input"
424
388
  rows="3"
425
389
  />
426
390
  </div>
427
- <div class="flex justify-end px-3 pb-3">
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="bg-[#07c160] hover:bg-[#06ad56] border-0 text-sm px-6"
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="flex-1 flex items-center justify-center flex-col bg-[#f5f5f5]"
416
+ class="empty-state"
453
417
  >
454
- <el-icon :size="64" class="text-gray-300 mb-2"><ChatLineRound /></el-icon>
455
- <div class="text-gray-400">
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="w-64 bg-[#f5f5f5] border-l border-gray-200 flex flex-col"
428
+ class="detail-panel"
465
429
  >
466
- <div class="h-14 flex items-center justify-center border-b border-gray-200">
467
- <span class="font-medium text-gray-700">聊天详情</span>
468
- </div>
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="w-20 h-20 rounded-full object-cover"
436
+ class="detail-avatar"
475
437
  />
476
- <div class="mt-3 font-medium text-gray-800 text-lg">{{ currentChat?.name }}</div>
477
- <div class="mt-6 w-full">
478
- <div class="bg-white rounded-lg">
479
- <div class="p-3 border-b border-gray-100 cursor-pointer hover:bg-gray-50">
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="mb-4">
500
- <div class="relative">
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="max-h-[400px] overflow-y-auto">
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="flex items-center justify-between p-3 hover:bg-gray-50 rounded-lg mb-2 transition-colors"
474
+ class="available-user-item"
519
475
  >
520
- <div class="flex items-center">
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="w-10 h-10 rounded-full object-cover"
480
+ class="available-user-avatar"
525
481
  />
526
- <div class="ml-3">
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="flex flex-col">
498
+ <div class="settings-container">
545
499
  <!-- 头像设置区域 -->
546
- <div class="flex flex-col items-center mb-8 pb-6 border-b border-gray-100">
547
- <div class="relative mb-4">
500
+ <div class="settings-avatar-section">
501
+ <div class="settings-avatar-wrapper">
548
502
  <img
549
503
  :src="myAvatar"
550
504
  alt="头像"
551
- class="w-28 h-28 rounded-full object-cover border-4 border-white shadow-lg"
505
+ class="settings-avatar"
552
506
  />
553
507
  <div
554
508
  v-if="config.modules.avatarCrop"
555
- class="absolute -bottom-1 -right-1 w-10 h-10 bg-green-500 rounded-full flex items-center justify-center cursor-pointer hover:bg-green-600 transition-all shadow-md"
509
+ class="settings-avatar-edit"
556
510
  @click="triggerAvatarUpload"
557
511
  >
558
- <el-icon :size="18" class="text-white"><Camera /></el-icon>
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="text-center">
569
- <div class="font-semibold text-gray-800 text-xl">{{ userInfo.nickname || myUsername }}</div>
570
- <div class="text-sm text-gray-500 mt-1">@{{ myUsername }}</div>
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="space-y-5">
576
- <div class="flex items-center justify-between mb-2">
577
- <div class="text-gray-700 font-semibold flex items-center gap-2">
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="rounded-full"
540
+ class="settings-edit-btn"
587
541
  >
588
542
  编辑
589
543
  </el-button>
590
544
  </div>
591
545
 
592
- <div class="bg-gray-50 rounded-xl p-6 space-y-5">
546
+ <div class="settings-form">
593
547
  <!-- 昵称 -->
594
- <div>
595
- <label class="block text-sm text-gray-600 mb-2 font-medium">昵称</label>
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="text-gray-800 bg-white rounded-lg px-4 py-3 border border-gray-200"
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="block text-sm text-gray-600 mb-2 font-medium">邮箱</label>
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="text-gray-800 bg-white rounded-lg px-4 py-3 border border-gray-200"
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="block text-sm text-gray-600 mb-2 font-medium">手机号</label>
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="text-gray-800 bg-white rounded-lg px-4 py-3 border border-gray-200"
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="block text-sm text-gray-600 mb-2 font-medium">个人简介</label>
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="text-gray-800 bg-white rounded-lg px-4 py-3 border border-gray-200 min-h-[80px]"
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="flex gap-3 justify-end pt-2">
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 fixed bg-white rounded-lg shadow-lg border py-1 z-50"
642
+ class="context-menu"
689
643
  :style="{ left: contextMenu.x + 'px', top: contextMenu.y + 'px' }"
690
644
  >
691
- <div class="px-4 py-2 hover:bg-gray-100 cursor-pointer" @click="handleRemoveChat">删除聊天</div>
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, inject } from 'vue'
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 api = new (require('../core/api.js').ChatApi)(props.config)
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
- .message-bubble-self {
1055
+ .bubble-self {
1138
1056
  position: relative;
1139
1057
  background-color: #95ec69 !important;
1140
1058
  }
1141
1059
 
1142
- .message-bubble-self::after {
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
- .message-bubble-other {
1072
+ .bubble-other {
1155
1073
  position: relative;
1156
1074
  background-color: white !important;
1157
1075
  }
1158
1076
 
1159
- .message-bubble-other::after {
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
- .message-list::-webkit-scrollbar {
1090
+ .messages-container::-webkit-scrollbar {
1173
1091
  width: 6px;
1174
1092
  }
1175
- .message-list::-webkit-scrollbar-thumb {
1093
+ .messages-container::-webkit-scrollbar-thumb {
1176
1094
  background: #ccc;
1177
1095
  border-radius: 3px;
1178
1096
  }
1179
- .message-list::-webkit-scrollbar-track {
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
- /* 简单的 flex 工具类 */
1201
- .flex { display: flex; }
1202
- .flex-col { flex-direction: column; }
1203
- .flex-1 { flex: 1; }
1204
- .items-center { align-items: center; }
1205
- .justify-center { justify-content: center; }
1206
- .justify-between { justify-content: space-between; }
1207
- .gap-2 { gap: 0.5rem; }
1208
- .gap-3 { gap: 0.75rem; }
1209
- .p-3 { padding: 0.75rem; }
1210
- .p-4 { padding: 1rem; }
1211
- .p-8 { padding: 2rem; }
1212
- .px-3 { padding-left: 0.75rem; padding-right: 0.75rem; }
1213
- .px-4 { padding-left: 1rem; padding-right: 1rem; }
1214
- .py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; }
1215
- .pb-3 { padding-bottom: 0.75rem; }
1216
- .mb-2 { margin-bottom: 0.5rem; }
1217
- .mb-4 { margin-bottom: 1rem; }
1218
- .mb-6 { margin-bottom: 1.5rem; }
1219
- .mb-8 { margin-bottom: 2rem; }
1220
- .mt-1 { margin-top: 0.25rem; }
1221
- .mt-3 { margin-top: 0.75rem; }
1222
- .mt-4 { margin-top: 1rem; }
1223
- .mt-6 { margin-top: 1.5rem; }
1224
- .mr-3 { margin-right: 0.75rem; }
1225
- .ml-3 { margin-left: 0.75rem; }
1226
- .w-10 { width: 2.5rem; }
1227
- .w-11 { width: 2.75rem; }
1228
- .w-16 { width: 4rem; }
1229
- .w-20 { width: 5rem; }
1230
- .w-24 { width: 6rem; }
1231
- .w-28 { width: 7rem; }
1232
- .w-64 { width: 16rem; }
1233
- .w-72 { width: 18rem; }
1234
- .w-full { width: 100%; }
1235
- .h-10 { height: 2.5rem; }
1236
- .h-11 { height: 2.75rem; }
1237
- .h-14 { height: 3.5rem; }
1238
- .h-20 { height: 5rem; }
1239
- .h-24 { height: 6rem; }
1240
- .h-28 { height: 7rem; }
1241
- .h-[680px] { height: 680px; }
1242
- .h-[80px] { height: 80px; }
1243
- .h-auto { height: auto; }
1244
- .h-full { height: 100%; }
1245
- .min-h-0 { min-height: 0; }
1246
- .max-w-\[300px\] { max-width: 300px; }
1247
- .max-w-\[75\%\] { max-width: 75%; }
1248
- .min-w-\[200px\] { min-width: 200px; }
1249
- .min-w-0 { min-width: 0; }
1250
- .rounded-full { border-radius: 9999px; }
1251
- .rounded-lg { border-radius: 0.5rem; }
1252
- .rounded-xl { border-radius: 0.75rem; }
1253
- .border { border-width: 1px; }
1254
- .border-0 { border-width: 0; }
1255
- .border-2 { border-width: 2px; }
1256
- .border-4 { border-width: 4px; }
1257
- .border-b { border-bottom-width: 1px; }
1258
- .border-l { border-left-width: 1px; }
1259
- .border-r { border-right-width: 1px; }
1260
- .border-gray-100 { border-color: #f3f4f6; }
1261
- .border-gray-200 { border-color: #e5e7eb; }
1262
- .border-white { border-color: #fff; }
1263
- .bg-\[\#07c160\] { background-color: #07c160; }
1264
- .bg-\[\#95ec69\] { background-color: #95ec69; }
1265
- .bg-\[\#f5f5f5\] { background-color: #f5f5f5; }
1266
- .bg-gray-50 { background-color: #f9fafb; }
1267
- .bg-gray-100 { background-color: #f3f4f6; }
1268
- .bg-green-100 { background-color: #dcfce7; }
1269
- .bg-green-50 { background-color: #f0fdf4; }
1270
- .bg-green-500 { background-color: #22c55e; }
1271
- .bg-red-500 { background-color: #ef4444; }
1272
- .bg-white { background-color: #fff; }
1273
- .hover\:bg-\[\#06ad56\]:hover { background-color: #06ad56; }
1274
- .hover\:bg-\[\#e5e5e5\]:hover { background-color: #e5e5e5; }
1275
- .hover\:bg-gray-100:hover { background-color: #f3f4f6; }
1276
- .hover\:bg-gray-50:hover { background-color: #f9fafb; }
1277
- .object-cover { object-fit: cover; }
1278
- .overflow-hidden { overflow: hidden; }
1279
- .overflow-y-auto { overflow-y: auto; }
1280
- .text-center { text-align: center; }
1281
- .text-right { text-align: right; }
1282
- .text-left { text-align: left; }
1283
- .text-xs { font-size: 0.75rem; }
1284
- .text-sm { font-size: 0.875rem; }
1285
- .text-lg { font-size: 1.125rem; }
1286
- .text-xl { font-size: 1.25rem; }
1287
- .font-medium { font-weight: 500; }
1288
- .font-semibold { font-weight: 600; }
1289
- .text-gray-300 { color: #d1d5db; }
1290
- .text-gray-400 { color: #9ca3af; }
1291
- .text-gray-500 { color: #6b7280; }
1292
- .text-gray-600 { color: #4b5563; }
1293
- .text-gray-700 { color: #374151; }
1294
- .text-gray-800 { color: #1f2937; }
1295
- .text-green-600 { color: #16a34a; }
1296
- .text-white { color: #fff; }
1297
- .cursor-pointer { cursor: pointer; }
1298
- .shadow-lg { box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); }
1299
- .shadow-sm { box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); }
1300
- .flex-wrap { flex-wrap: wrap; }
1301
- .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1302
- .break-all { word-break: break-all; }
1303
- .whitespace-pre-wrap { white-space: pre-wrap; }
1304
- .select-none { user-select: none; }
1305
- .transition-all { transition-property: all; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms; }
1306
- .transition-colors { transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms; }
1307
- .self-end { align-self: flex-end; }
1308
- .self-start { align-self: flex-start; }
1309
- .flex-shrink-0 { flex-shrink: 0; }
1310
- .flex-row-reverse { flex-direction: row-reverse; }
1311
- .flex-row { flex-direction: row; }
1312
- .inset-0 { top: 0; right: 0; bottom: 0; left: 0; }
1313
- .absolute { position: absolute; }
1314
- .relative { position: relative; }
1315
- .fixed { position: fixed; }
1316
- .top-0 { top: 0; }
1317
- .bottom-0 { bottom: 0; }
1318
- .left-0 { left: 0; }
1319
- .right-0 { right: 0; }
1320
- .top-1 { top: 0.25rem; }
1321
- .right-1 { right: 0.25rem; }
1322
- .-top-1 { top: -0.25rem; }
1323
- .-right-1 { right: -0.25rem; }
1324
- .-bottom-1 { bottom: -0.25rem; }
1325
- .z-50 { z-index: 50; }
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>