vue-chat-kit 0.3.7 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -4
- package/dist/vue-chat-kit.css +1 -1
- package/dist/vue-chat-kit.es.js +3392 -2122
- package/dist/vue-chat-kit.umd.js +1 -1
- package/package.json +1 -1
- package/src/components/ChatPanel.vue +1570 -57
- package/src/components/EmojiPicker.vue +197 -0
- package/src/composables/useChat.js +157 -613
- package/src/composables/useChatCore.js +207 -0
- package/src/composables/useFriendChat.js +423 -0
- package/src/composables/useGroupChat.js +748 -0
- package/src/config/index.js +21 -4
- package/src/core/adapter-example.js +90 -20
- package/src/core/api.js +189 -13
- package/src/core/websocket.js +25 -9
- package/src/index.js +0 -4
- package/src/components/ChatWindow.vue +0 -2094
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 聊天核心共享逻辑
|
|
3
|
+
*/
|
|
4
|
+
import { ref, computed, nextTick } from 'vue'
|
|
5
|
+
import dayjs from 'dayjs'
|
|
6
|
+
import { ChatWebSocket } from '../core/websocket.js'
|
|
7
|
+
import { ChatApi } from '../core/api.js'
|
|
8
|
+
|
|
9
|
+
export function useChatCore(config, emit) {
|
|
10
|
+
// ========== 配置 ==========
|
|
11
|
+
const api = new ChatApi(config)
|
|
12
|
+
let socket = null
|
|
13
|
+
|
|
14
|
+
// ========== 共享状态 ==========
|
|
15
|
+
const myUsername = config.user.username
|
|
16
|
+
const myAvatar = ref(config.user.avatar || `https://api.dicebear.com/7.x/avataaars/svg?seed=${myUsername}`)
|
|
17
|
+
|
|
18
|
+
// 用户信息
|
|
19
|
+
const userInfo = ref({
|
|
20
|
+
username: myUsername,
|
|
21
|
+
nickname: config.user.nickname || '',
|
|
22
|
+
email: config.user.email || '',
|
|
23
|
+
phone: config.user.phone || '',
|
|
24
|
+
bio: config.user.bio || ''
|
|
25
|
+
})
|
|
26
|
+
const loadingUserInfo = ref(false)
|
|
27
|
+
|
|
28
|
+
// UI 共享状态
|
|
29
|
+
const searchText = ref('')
|
|
30
|
+
const inputText = ref('')
|
|
31
|
+
const messagesContainer = ref(null)
|
|
32
|
+
|
|
33
|
+
// ========== 共享计算属性 ==========
|
|
34
|
+
|
|
35
|
+
// ========== 共享工具函数 ==========
|
|
36
|
+
|
|
37
|
+
const formatTime = (time) => dayjs(time).format('HH:mm')
|
|
38
|
+
|
|
39
|
+
const formatLastTime = (time) => {
|
|
40
|
+
if (!time) return ''
|
|
41
|
+
const now = dayjs()
|
|
42
|
+
const msgTime = dayjs(time)
|
|
43
|
+
if (now.isSame(msgTime, 'day')) return msgTime.format('HH:mm')
|
|
44
|
+
if (now.diff(msgTime, 'day') === 1) return '昨天'
|
|
45
|
+
if (now.diff(msgTime, 'day') < 7) {
|
|
46
|
+
const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
|
47
|
+
return weekDays[msgTime.day()]
|
|
48
|
+
}
|
|
49
|
+
return msgTime.format('MM/DD')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const isImageFile = (fileName) => {
|
|
53
|
+
if (!fileName) return false
|
|
54
|
+
const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg']
|
|
55
|
+
const ext = fileName.split('.').pop().toLowerCase()
|
|
56
|
+
return imageExts.includes(ext)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const getFileIconType = (fileName) => {
|
|
60
|
+
if (!fileName) return 'default'
|
|
61
|
+
const ext = fileName.split('.').pop().toLowerCase()
|
|
62
|
+
if (['xls', 'xlsx'].includes(ext)) return 'excel'
|
|
63
|
+
if (['pdf'].includes(ext)) return 'pdf'
|
|
64
|
+
if (['doc', 'docx'].includes(ext)) return 'docx'
|
|
65
|
+
return 'default'
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const scrollToBottom = () => {
|
|
69
|
+
nextTick(() => {
|
|
70
|
+
if (messagesContainer.value) {
|
|
71
|
+
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ========== WebSocket 共享逻辑 ==========
|
|
77
|
+
|
|
78
|
+
// 解析 WebSocket 消息
|
|
79
|
+
const parseWsMessage = (data) => {
|
|
80
|
+
try {
|
|
81
|
+
try {
|
|
82
|
+
const jsonData = JSON.parse(data)
|
|
83
|
+
if (jsonData.to || jsonData.toGroupId || jsonData.msg) {
|
|
84
|
+
return {
|
|
85
|
+
to: jsonData.to,
|
|
86
|
+
toGroupId: jsonData.toGroupId,
|
|
87
|
+
from: jsonData.from,
|
|
88
|
+
content: jsonData.msg,
|
|
89
|
+
type: jsonData.type || 'text',
|
|
90
|
+
fileUrl: jsonData.fileUrl || '',
|
|
91
|
+
fileName: jsonData.fileName || '',
|
|
92
|
+
fileSize: jsonData.fileSize || 0
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
// 旧格式
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const match = data.match(/^\[(.+?)\]:(.+)$/)
|
|
100
|
+
if (match) {
|
|
101
|
+
return {
|
|
102
|
+
username: match[1],
|
|
103
|
+
content: match[2],
|
|
104
|
+
type: 'text'
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
} catch (e) {
|
|
108
|
+
console.error('[VueChatKit] 解析消息失败', e)
|
|
109
|
+
}
|
|
110
|
+
return null
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 初始化 WebSocket
|
|
114
|
+
const initWebSocket = (messageHandler) => {
|
|
115
|
+
const wsUrl = `${config.api.websocketUrl}?userId=${myUsername}`
|
|
116
|
+
socket = new ChatWebSocket(myUsername, {
|
|
117
|
+
wsUrl,
|
|
118
|
+
maxReconnectAttempts: config.websocket.maxReconnectAttempts,
|
|
119
|
+
reconnectDelay: config.websocket.reconnectDelay
|
|
120
|
+
})
|
|
121
|
+
socket.on('message', messageHandler)
|
|
122
|
+
socket.connect()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 关闭 WebSocket
|
|
126
|
+
const closeWebSocket = () => {
|
|
127
|
+
if (socket) {
|
|
128
|
+
socket.close()
|
|
129
|
+
socket = null
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 获取 socket 实例
|
|
134
|
+
const getSocket = () => socket
|
|
135
|
+
|
|
136
|
+
// ========== 用户信息相关 ==========
|
|
137
|
+
|
|
138
|
+
const initUserAvatar = async () => {
|
|
139
|
+
try {
|
|
140
|
+
const res = await api.getUserAvatar(myUsername)
|
|
141
|
+
if (res.code === 200 && res.data) {
|
|
142
|
+
myAvatar.value = `${config.api.baseUrl}${res.data}`
|
|
143
|
+
}
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.warn('[VueChatKit] 加载头像失败', error)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const updateMyAvatar = (avatarUrl) => {
|
|
150
|
+
myAvatar.value = avatarUrl
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const updateUserInfo = async (data) => {
|
|
154
|
+
try {
|
|
155
|
+
const res = await api.updateUserInfo(myUsername, data)
|
|
156
|
+
if (res.code === 200) {
|
|
157
|
+
userInfo.value = {
|
|
158
|
+
...userInfo.value,
|
|
159
|
+
...data
|
|
160
|
+
}
|
|
161
|
+
return true
|
|
162
|
+
}
|
|
163
|
+
return false
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.error('[VueChatKit] 更新用户信息失败', error)
|
|
166
|
+
return false
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 初始化
|
|
171
|
+
initUserAvatar()
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
// 配置
|
|
175
|
+
api,
|
|
176
|
+
config,
|
|
177
|
+
|
|
178
|
+
// 状态
|
|
179
|
+
myUsername,
|
|
180
|
+
myAvatar,
|
|
181
|
+
userInfo,
|
|
182
|
+
loadingUserInfo,
|
|
183
|
+
searchText,
|
|
184
|
+
inputText,
|
|
185
|
+
messagesContainer,
|
|
186
|
+
|
|
187
|
+
// 工具函数
|
|
188
|
+
formatTime,
|
|
189
|
+
formatLastTime,
|
|
190
|
+
isImageFile,
|
|
191
|
+
getFileIconType,
|
|
192
|
+
scrollToBottom,
|
|
193
|
+
|
|
194
|
+
// WebSocket
|
|
195
|
+
parseWsMessage,
|
|
196
|
+
initWebSocket,
|
|
197
|
+
closeWebSocket,
|
|
198
|
+
getSocket,
|
|
199
|
+
|
|
200
|
+
// 用户信息
|
|
201
|
+
initUserAvatar,
|
|
202
|
+
updateMyAvatar,
|
|
203
|
+
updateUserInfo
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export default useChatCore
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 私聊逻辑
|
|
3
|
+
*/
|
|
4
|
+
import { ref, computed, watch } from 'vue'
|
|
5
|
+
|
|
6
|
+
export function useFriendChat(core) {
|
|
7
|
+
const { api, myUsername, myAvatar, config, scrollToBottom, getSocket } = core
|
|
8
|
+
|
|
9
|
+
// ========== 状态 ==========
|
|
10
|
+
const friendList = ref([])
|
|
11
|
+
const chatList = ref([])
|
|
12
|
+
const chatMsgList = ref([])
|
|
13
|
+
const currentSelectName = ref('')
|
|
14
|
+
|
|
15
|
+
// 添加好友相关
|
|
16
|
+
const addFriendDialogVisible = ref(false)
|
|
17
|
+
const addFriendSearchText = ref('')
|
|
18
|
+
const availableUsers = ref([])
|
|
19
|
+
const loadingAvailableUsers = ref(false)
|
|
20
|
+
|
|
21
|
+
// 好友申请
|
|
22
|
+
const friendApplyList = ref([])
|
|
23
|
+
const loadingFriendApply = ref(false)
|
|
24
|
+
|
|
25
|
+
// ========== 计算属性 ==========
|
|
26
|
+
|
|
27
|
+
// 过滤后的聊天列表
|
|
28
|
+
const filteredUsers = computed(() => {
|
|
29
|
+
let list = chatList.value
|
|
30
|
+
if (core.searchText.value) {
|
|
31
|
+
const keyword = core.searchText.value.toLowerCase()
|
|
32
|
+
list = list.filter(item =>
|
|
33
|
+
item.username?.toLowerCase().includes(keyword)
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
return list.map(item => ({
|
|
37
|
+
id: item.username,
|
|
38
|
+
name: item.username,
|
|
39
|
+
avatar: item.avatar ? `${config.api.baseUrl}${item.avatar}` : myAvatar.value,
|
|
40
|
+
online: item.online,
|
|
41
|
+
lastMsg: item.lastMsg || '暂无消息',
|
|
42
|
+
lastTime: item.lastTime,
|
|
43
|
+
unread: item.unReadNum || 0
|
|
44
|
+
}))
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
// 过滤后的好友列表
|
|
48
|
+
const filteredFriendList = computed(() => {
|
|
49
|
+
let list = friendList.value
|
|
50
|
+
if (core.searchText.value) {
|
|
51
|
+
const keyword = core.searchText.value.toLowerCase()
|
|
52
|
+
list = list.filter(item =>
|
|
53
|
+
item.username?.toLowerCase().includes(keyword)
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
return list.map(item => ({
|
|
57
|
+
id: item.username,
|
|
58
|
+
name: item.username,
|
|
59
|
+
avatar: item.avatar ? `${config.api.baseUrl}${item.avatar}` : myAvatar.value,
|
|
60
|
+
online: item.online,
|
|
61
|
+
isChatting: item.isChatting
|
|
62
|
+
}))
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// 过滤后的可添加用户
|
|
66
|
+
const filteredAvailableUsers = computed(() => {
|
|
67
|
+
return availableUsers.value
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
// 当前选中的用户
|
|
71
|
+
const currentUser = computed(() => {
|
|
72
|
+
return filteredUsers.value.find(item => item.id === currentSelectName.value) || null
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
// 处理后的消息列表
|
|
76
|
+
const currentMessages = computed(() => {
|
|
77
|
+
return chatMsgList.value.map(item => {
|
|
78
|
+
const isFileMessage = item.type === 'file' || item.fileUrl || item.fileName
|
|
79
|
+
const fileName = item.fileName || item.msgContent
|
|
80
|
+
// 如果是对方发送的消息,从好友列表中查找头像
|
|
81
|
+
let avatar = ''
|
|
82
|
+
if (item.sendUsername !== myUsername) {
|
|
83
|
+
const friend = friendList.value.find(f => f.username === item.sendUsername)
|
|
84
|
+
avatar = friend?.avatar
|
|
85
|
+
? `${config.api.baseUrl}${friend.avatar}`
|
|
86
|
+
: `https://api.dicebear.com/7.x/avataaars/svg?seed=${item.sendUsername}`
|
|
87
|
+
} else {
|
|
88
|
+
avatar = myAvatar.value
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
text: item.msgContent,
|
|
93
|
+
isSelf: item.sendUsername === myUsername,
|
|
94
|
+
time: item.createTime,
|
|
95
|
+
sendUsername: item.sendUsername,
|
|
96
|
+
avatar: avatar,
|
|
97
|
+
type: isFileMessage ? 'file' : 'text',
|
|
98
|
+
fileType: core.isImageFile(fileName) ? 'image' : core.getFileIconType(fileName),
|
|
99
|
+
fileUrl: item.fileUrl || '',
|
|
100
|
+
fileName: fileName,
|
|
101
|
+
fileSize: item.fileSize || 0
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// ========== API 方法 ==========
|
|
107
|
+
|
|
108
|
+
// 获取好友列表
|
|
109
|
+
const getFriendList = async () => {
|
|
110
|
+
try {
|
|
111
|
+
const res = await api.getFriends(myUsername)
|
|
112
|
+
const allFriends = res.data || []
|
|
113
|
+
friendList.value = allFriends
|
|
114
|
+
chatList.value = allFriends.filter(item => item.isChatting === 1)
|
|
115
|
+
|
|
116
|
+
// 获取未读数
|
|
117
|
+
for (const friend of chatList.value) {
|
|
118
|
+
try {
|
|
119
|
+
const historyRes = await api.getHistory(myUsername, friend.username)
|
|
120
|
+
const messages = historyRes.data || []
|
|
121
|
+
friend.unReadNum = messages.filter(msg =>
|
|
122
|
+
msg.isRead === 0 && msg.sendUsername === friend.username
|
|
123
|
+
).length
|
|
124
|
+
} catch {
|
|
125
|
+
friend.unReadNum = 0
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error('[VueChatKit] 获取好友列表失败', error)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 获取聊天历史
|
|
134
|
+
const getChatHistory = async (targetName) => {
|
|
135
|
+
try {
|
|
136
|
+
const res = await api.getHistory(myUsername, targetName)
|
|
137
|
+
chatMsgList.value = res.data || []
|
|
138
|
+
scrollToBottom()
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error('[VueChatKit] 获取聊天历史失败', error)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 标记已读
|
|
145
|
+
const markAsRead = async (friendUser) => {
|
|
146
|
+
try {
|
|
147
|
+
await api.setRead(myUsername, friendUser)
|
|
148
|
+
getFriendList()
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error('[VueChatKit] 标记已读失败', error)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 选择聊天用户
|
|
155
|
+
const selectUser = async (user) => {
|
|
156
|
+
currentSelectName.value = user.id
|
|
157
|
+
await getChatHistory(user.id)
|
|
158
|
+
await markAsRead(user.id)
|
|
159
|
+
scrollToBottom()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 设置好友聊天状态
|
|
163
|
+
const setFriendToChatStatus = async (friendUser, status = 1) => {
|
|
164
|
+
try {
|
|
165
|
+
await api.setChatStatus(myUsername, friendUser, status)
|
|
166
|
+
await getFriendList()
|
|
167
|
+
return true
|
|
168
|
+
} catch (error) {
|
|
169
|
+
console.error('[VueChatKit] 设置聊天状态失败', error)
|
|
170
|
+
return false
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 发送文本消息
|
|
175
|
+
const sendMessage = () => {
|
|
176
|
+
const socket = getSocket()
|
|
177
|
+
if (!core.inputText.value.trim() || !currentSelectName.value || !socket) return
|
|
178
|
+
|
|
179
|
+
const success = socket.send(currentSelectName.value, core.inputText.value.trim(), 'text')
|
|
180
|
+
if (success) {
|
|
181
|
+
const tempMsg = {
|
|
182
|
+
msgContent: core.inputText.value.trim(),
|
|
183
|
+
sendUsername: myUsername,
|
|
184
|
+
receiveUsername: currentSelectName.value,
|
|
185
|
+
createTime: new Date(),
|
|
186
|
+
isRead: 0,
|
|
187
|
+
type: 'text'
|
|
188
|
+
}
|
|
189
|
+
chatMsgList.value.push(tempMsg)
|
|
190
|
+
core.inputText.value = ''
|
|
191
|
+
scrollToBottom()
|
|
192
|
+
setTimeout(() => {
|
|
193
|
+
getChatHistory(currentSelectName.value)
|
|
194
|
+
getFriendList()
|
|
195
|
+
}, 300)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 发送文件
|
|
200
|
+
const sendFile = async (file) => {
|
|
201
|
+
const socket = getSocket()
|
|
202
|
+
if (!currentSelectName.value || !socket) return false
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
const uploadRes = await api.uploadFile(file)
|
|
206
|
+
|
|
207
|
+
if (uploadRes.code === 200 && uploadRes.data) {
|
|
208
|
+
const { fileUrl, fileName } = uploadRes.data
|
|
209
|
+
|
|
210
|
+
const success = socket.send(
|
|
211
|
+
currentSelectName.value,
|
|
212
|
+
fileName,
|
|
213
|
+
'file',
|
|
214
|
+
fileUrl,
|
|
215
|
+
fileName,
|
|
216
|
+
file.size
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
if (success) {
|
|
220
|
+
const tempMsg = {
|
|
221
|
+
msgContent: fileName,
|
|
222
|
+
sendUsername: myUsername,
|
|
223
|
+
receiveUsername: currentSelectName.value,
|
|
224
|
+
createTime: new Date(),
|
|
225
|
+
isRead: 0,
|
|
226
|
+
type: 'file',
|
|
227
|
+
fileUrl: fileUrl,
|
|
228
|
+
fileName: fileName,
|
|
229
|
+
fileSize: file.size
|
|
230
|
+
}
|
|
231
|
+
chatMsgList.value.push(tempMsg)
|
|
232
|
+
scrollToBottom()
|
|
233
|
+
return true
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return false
|
|
237
|
+
} catch (error) {
|
|
238
|
+
console.error('[VueChatKit] 发送文件失败', error)
|
|
239
|
+
return false
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// 批量发送文件和文本
|
|
244
|
+
const sendFilesAndText = async (files, text) => {
|
|
245
|
+
const socket = getSocket()
|
|
246
|
+
if (!currentSelectName.value || !socket) return
|
|
247
|
+
|
|
248
|
+
if (text && text.trim()) {
|
|
249
|
+
const textSuccess = socket.send(currentSelectName.value, text.trim(), 'text')
|
|
250
|
+
if (textSuccess) {
|
|
251
|
+
const tempMsg = {
|
|
252
|
+
msgContent: text.trim(),
|
|
253
|
+
sendUsername: myUsername,
|
|
254
|
+
receiveUsername: currentSelectName.value,
|
|
255
|
+
createTime: new Date(),
|
|
256
|
+
isRead: 0,
|
|
257
|
+
type: 'text'
|
|
258
|
+
}
|
|
259
|
+
chatMsgList.value.push(tempMsg)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
for (const fileObj of files) {
|
|
264
|
+
const file = fileObj.file || fileObj
|
|
265
|
+
await sendFile(file)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
setTimeout(() => {
|
|
269
|
+
getChatHistory(currentSelectName.value)
|
|
270
|
+
getFriendList()
|
|
271
|
+
}, 300)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// 处理单聊 WebSocket 消息
|
|
275
|
+
const handleFriendWsMessage = (msgInfo) => {
|
|
276
|
+
const message = {
|
|
277
|
+
content: msgInfo.content,
|
|
278
|
+
username: msgInfo.username || msgInfo.from || currentSelectName.value,
|
|
279
|
+
type: msgInfo.type || 'text',
|
|
280
|
+
fileUrl: msgInfo.fileUrl || '',
|
|
281
|
+
fileName: msgInfo.fileName || '',
|
|
282
|
+
fileSize: msgInfo.fileSize || 0,
|
|
283
|
+
timestamp: new Date()
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (currentSelectName.value) {
|
|
287
|
+
try {
|
|
288
|
+
let tempMsg = {
|
|
289
|
+
msgContent: msgInfo.content,
|
|
290
|
+
sendUsername: message.username,
|
|
291
|
+
receiveUsername: myUsername,
|
|
292
|
+
createTime: new Date(),
|
|
293
|
+
isRead: 0,
|
|
294
|
+
type: msgInfo.type || 'text',
|
|
295
|
+
fileUrl: msgInfo.fileUrl || '',
|
|
296
|
+
fileName: msgInfo.fileName || '',
|
|
297
|
+
fileSize: msgInfo.fileSize || 0
|
|
298
|
+
}
|
|
299
|
+
chatMsgList.value.push(tempMsg)
|
|
300
|
+
scrollToBottom()
|
|
301
|
+
} catch (e) {
|
|
302
|
+
console.error('[VueChatKit] 添加临时消息失败', e)
|
|
303
|
+
}
|
|
304
|
+
getChatHistory(currentSelectName.value)
|
|
305
|
+
}
|
|
306
|
+
getFriendList()
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// 处理好友状态变更
|
|
310
|
+
const handleFriendStatusChange = (targetName, isOnline) => {
|
|
311
|
+
const targetFriend = friendList.value.find(item => item.username === targetName)
|
|
312
|
+
if (targetFriend) {
|
|
313
|
+
targetFriend.online = isOnline
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ========== 添加好友相关 ==========
|
|
318
|
+
|
|
319
|
+
const openAddFriendDialog = async () => {
|
|
320
|
+
addFriendDialogVisible.value = true
|
|
321
|
+
addFriendSearchText.value = ''
|
|
322
|
+
availableUsers.value = []
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const loadAvailableUsers = async (keyword = '') => {
|
|
326
|
+
loadingAvailableUsers.value = true
|
|
327
|
+
try {
|
|
328
|
+
let res
|
|
329
|
+
if (keyword) {
|
|
330
|
+
res = await api.searchUser(keyword)
|
|
331
|
+
} else {
|
|
332
|
+
res = await api.getAvailableUsers(myUsername)
|
|
333
|
+
}
|
|
334
|
+
availableUsers.value = res?.data || []
|
|
335
|
+
} catch (error) {
|
|
336
|
+
console.error('[VueChatKit] 获取可用用户失败', error)
|
|
337
|
+
} finally {
|
|
338
|
+
loadingAvailableUsers.value = false
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const addFriend = async (user) => {
|
|
343
|
+
try {
|
|
344
|
+
await api.addFriend(myUsername, user.username)
|
|
345
|
+
await getFriendList()
|
|
346
|
+
addFriendDialogVisible.value = false
|
|
347
|
+
} catch (error) {
|
|
348
|
+
console.error('[VueChatKit] 添加好友失败', error)
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const loadFriendApplyList = async () => {
|
|
353
|
+
loadingFriendApply.value = true
|
|
354
|
+
try {
|
|
355
|
+
const res = await api.getApplyList(myUsername)
|
|
356
|
+
friendApplyList.value = res.data || []
|
|
357
|
+
} catch (error) {
|
|
358
|
+
console.error('[VueChatKit] 获取好友申请列表失败', error)
|
|
359
|
+
} finally {
|
|
360
|
+
loadingFriendApply.value = false
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const agreeFriend = async (applyUser) => {
|
|
365
|
+
try {
|
|
366
|
+
await api.agreeFriend(applyUser, myUsername)
|
|
367
|
+
await loadFriendApplyList()
|
|
368
|
+
await getFriendList()
|
|
369
|
+
} catch (error) {
|
|
370
|
+
console.error('[VueChatKit] 同意好友申请失败', error)
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// 重置私聊状态
|
|
375
|
+
const resetFriendChat = () => {
|
|
376
|
+
currentSelectName.value = ''
|
|
377
|
+
chatMsgList.value = []
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// 监听添加好友搜索关键词变化
|
|
381
|
+
watch(addFriendSearchText, async (newKeyword) => {
|
|
382
|
+
await loadAvailableUsers(newKeyword)
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
return {
|
|
386
|
+
// 状态
|
|
387
|
+
friendList,
|
|
388
|
+
chatList,
|
|
389
|
+
chatMsgList,
|
|
390
|
+
currentSelectName,
|
|
391
|
+
filteredUsers,
|
|
392
|
+
filteredFriendList,
|
|
393
|
+
filteredAvailableUsers,
|
|
394
|
+
currentUser,
|
|
395
|
+
currentMessages,
|
|
396
|
+
addFriendDialogVisible,
|
|
397
|
+
addFriendSearchText,
|
|
398
|
+
availableUsers,
|
|
399
|
+
loadingAvailableUsers,
|
|
400
|
+
friendApplyList,
|
|
401
|
+
loadingFriendApply,
|
|
402
|
+
|
|
403
|
+
// 方法
|
|
404
|
+
getFriendList,
|
|
405
|
+
getChatHistory,
|
|
406
|
+
markAsRead,
|
|
407
|
+
selectUser,
|
|
408
|
+
setFriendToChatStatus,
|
|
409
|
+
sendMessage,
|
|
410
|
+
sendFile,
|
|
411
|
+
sendFilesAndText,
|
|
412
|
+
handleFriendWsMessage,
|
|
413
|
+
handleFriendStatusChange,
|
|
414
|
+
openAddFriendDialog,
|
|
415
|
+
loadAvailableUsers,
|
|
416
|
+
addFriend,
|
|
417
|
+
loadFriendApplyList,
|
|
418
|
+
agreeFriend,
|
|
419
|
+
resetFriendChat
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export default useFriendChat
|