vue-chat-kit 0.1.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.
@@ -0,0 +1,189 @@
1
+ /**
2
+ * API 服务层
3
+ */
4
+ import { HttpClient } from './request.js'
5
+
6
+ export class ChatApi {
7
+ constructor(config, httpClient = null) {
8
+ this.config = config
9
+ this.endpoints = config.api.endpoints
10
+ this.customAdapter = config.api.adapter
11
+
12
+ if (httpClient) {
13
+ this.http = httpClient
14
+ } else {
15
+ this.http = new HttpClient({
16
+ baseUrl: config.api.baseUrl,
17
+ headers: config.headers
18
+ })
19
+ }
20
+ }
21
+
22
+ /**
23
+ * 使用自定义适配器或默认实现
24
+ */
25
+ async _call(methodName, ...args) {
26
+ if (this.customAdapter && typeof this.customAdapter[methodName] === 'function') {
27
+ return this.customAdapter[methodName](...args)
28
+ }
29
+ return this[`_${methodName}`](...args)
30
+ }
31
+
32
+ // ========== 好友相关 ==========
33
+
34
+ /**
35
+ * 获取好友列表
36
+ */
37
+ async getFriends(currentUser) {
38
+ return this._call('getFriends', currentUser)
39
+ }
40
+
41
+ async _getFriends(currentUser) {
42
+ return this.http.get(this.endpoints.getFriends, { currentUser })
43
+ }
44
+
45
+ /**
46
+ * 获取可添加的用户列表
47
+ */
48
+ async getAvailableUsers(currentUser) {
49
+ return this._call('getAvailableUsers', currentUser)
50
+ }
51
+
52
+ async _getAvailableUsers(currentUser) {
53
+ return this.http.get(this.endpoints.getAvailableUsers, { currentUser })
54
+ }
55
+
56
+ /**
57
+ * 添加好友
58
+ */
59
+ async addFriend(currentUser, friendUser) {
60
+ return this._call('addFriend', currentUser, friendUser)
61
+ }
62
+
63
+ async _addFriend(currentUser, friendUser) {
64
+ return this.http.post(this.endpoints.addFriend, { currentUser, friendUser })
65
+ }
66
+
67
+ /**
68
+ * 获取好友申请列表
69
+ */
70
+ async getApplyList(currentUser) {
71
+ return this._call('getApplyList', currentUser)
72
+ }
73
+
74
+ async _getApplyList(currentUser) {
75
+ return this.http.get(this.endpoints.getApplyList, { currentUser })
76
+ }
77
+
78
+ /**
79
+ * 同意好友申请
80
+ */
81
+ async agreeFriend(applyUser, friendUser) {
82
+ return this._call('agreeFriend', applyUser, friendUser)
83
+ }
84
+
85
+ async _agreeFriend(applyUser, friendUser) {
86
+ return this.http.post(this.endpoints.agreeFriend, { applyUser, friendUser })
87
+ }
88
+
89
+ /**
90
+ * 设置好友聊天状态
91
+ */
92
+ async setChatStatus(currentUser, friendUser, status = 1) {
93
+ return this._call('setChatStatus', currentUser, friendUser, status)
94
+ }
95
+
96
+ async _setChatStatus(currentUser, friendUser, status) {
97
+ return this.http.post(this.endpoints.setChatStatus, null, { params: { currentUser, friendUser, status } })
98
+ }
99
+
100
+ // ========== 消息相关 ==========
101
+
102
+ /**
103
+ * 获取聊天历史
104
+ */
105
+ async getHistory(fromUser, toUser) {
106
+ return this._call('getHistory', fromUser, toUser)
107
+ }
108
+
109
+ async _getHistory(fromUser, toUser) {
110
+ return this.http.get(this.endpoints.getHistory, { fromUser, toUser })
111
+ }
112
+
113
+ /**
114
+ * 标记消息已读
115
+ */
116
+ async setRead(currentUser, friendUser) {
117
+ return this._call('setRead', currentUser, friendUser)
118
+ }
119
+
120
+ async _setRead(currentUser, friendUser) {
121
+ return this.http.post(this.endpoints.setRead, { currentUser, friendUser })
122
+ }
123
+
124
+ // ========== 文件相关 ==========
125
+
126
+ /**
127
+ * 上传文件
128
+ */
129
+ async uploadFile(file) {
130
+ return this._call('uploadFile', file)
131
+ }
132
+
133
+ async _uploadFile(file) {
134
+ const formData = new FormData()
135
+ formData.append('file', file)
136
+ return this.http.post(this.endpoints.uploadFile, formData)
137
+ }
138
+
139
+ // ========== 用户相关 ==========
140
+
141
+ /**
142
+ * 获取用户信息
143
+ */
144
+ async getUserInfo(username) {
145
+ return this._call('getUserInfo', username)
146
+ }
147
+
148
+ async _getUserInfo(username) {
149
+ return this.http.get(this.endpoints.getUserInfo, { username })
150
+ }
151
+
152
+ /**
153
+ * 更新用户信息
154
+ */
155
+ async updateUserInfo(username, data) {
156
+ return this._call('updateUserInfo', username, data)
157
+ }
158
+
159
+ async _updateUserInfo(username, data) {
160
+ return this.http.put(this.endpoints.updateUserInfo, { username, ...data })
161
+ }
162
+
163
+ /**
164
+ * 获取用户头像
165
+ */
166
+ async getUserAvatar(username) {
167
+ return this._call('getUserAvatar', username)
168
+ }
169
+
170
+ async _getUserAvatar(username) {
171
+ return this.http.get(this.endpoints.getUserAvatar, { username })
172
+ }
173
+
174
+ /**
175
+ * 上传头像
176
+ */
177
+ async uploadAvatar(file, username) {
178
+ return this._call('uploadAvatar', file, username)
179
+ }
180
+
181
+ async _uploadAvatar(file, username) {
182
+ const formData = new FormData()
183
+ formData.append('file', file)
184
+ formData.append('username', username)
185
+ return this.http.post(this.endpoints.uploadAvatar, formData)
186
+ }
187
+ }
188
+
189
+ export default ChatApi
@@ -0,0 +1,174 @@
1
+ /**
2
+ * HTTP 请求工具
3
+ */
4
+ class RequestError extends Error {
5
+ constructor(message, status, code, data) {
6
+ super(message)
7
+ this.name = 'RequestError'
8
+ this.status = status || 0
9
+ this.code = code || 0
10
+ this.data = data
11
+ }
12
+ }
13
+
14
+ /**
15
+ * 请求类
16
+ */
17
+ export class HttpClient {
18
+ constructor(config = {}) {
19
+ this.baseUrl = config.baseUrl || ''
20
+ this.timeout = config.timeout || 10000
21
+ this.headers = config.headers || {}
22
+ this.requestInterceptors = []
23
+ this.responseInterceptors = []
24
+
25
+ // 默认添加 Content-Type
26
+ this.addRequestInterceptor((config) => {
27
+ if (!(config.body instanceof FormData)) {
28
+ config.headers = {
29
+ 'Content-Type': 'application/json',
30
+ ...config.headers
31
+ }
32
+ }
33
+ return config
34
+ })
35
+ }
36
+
37
+ /**
38
+ * 添加请求拦截器
39
+ */
40
+ addRequestInterceptor(interceptor) {
41
+ this.requestInterceptors.push(interceptor)
42
+ }
43
+
44
+ /**
45
+ * 添加响应拦截器
46
+ */
47
+ addResponseInterceptor(interceptor) {
48
+ this.responseInterceptors.push(interceptor)
49
+ }
50
+
51
+ /**
52
+ * 超时控制
53
+ */
54
+ timeoutPromise(timeout) {
55
+ return new Promise((_, reject) => {
56
+ setTimeout(() => {
57
+ reject(new RequestError('请求超时', 408, 408))
58
+ }, timeout)
59
+ })
60
+ }
61
+
62
+ /**
63
+ * 核心请求方法
64
+ */
65
+ async request(url, options = {}) {
66
+ const { method = 'GET', headers = {}, body, params } = options
67
+
68
+ // 构建完整URL
69
+ let fullUrl = url.startsWith('http') ? url : `${this.baseUrl}${url}`
70
+
71
+ // 应用请求拦截器
72
+ let finalConfig = {
73
+ method,
74
+ headers: { ...this.headers, ...headers },
75
+ body,
76
+ params
77
+ }
78
+
79
+ this.requestInterceptors.forEach((interceptor) => {
80
+ finalConfig = interceptor(finalConfig)
81
+ })
82
+
83
+ // 构建fetch配置
84
+ const fetchConfig = {
85
+ method: finalConfig.method,
86
+ headers: finalConfig.headers,
87
+ credentials: 'include'
88
+ }
89
+
90
+ // 处理请求体
91
+ if (finalConfig.body && method !== 'GET') {
92
+ if (finalConfig.body instanceof FormData) {
93
+ fetchConfig.body = finalConfig.body
94
+ } else if (typeof finalConfig.body === 'object') {
95
+ fetchConfig.body = JSON.stringify(finalConfig.body)
96
+ } else {
97
+ fetchConfig.body = finalConfig.body
98
+ }
99
+ }
100
+
101
+ // 处理GET参数
102
+ if (finalConfig.params) {
103
+ const queryParams = new URLSearchParams()
104
+ for (const key in finalConfig.params) {
105
+ if (finalConfig.params[key] !== undefined && finalConfig.params[key] !== null && finalConfig.params[key] !== '') {
106
+ queryParams.append(key, finalConfig.params[key])
107
+ }
108
+ }
109
+ const queryString = queryParams.toString()
110
+ if (queryString) {
111
+ fullUrl += (fullUrl.includes('?') ? '&' : '?') + queryString
112
+ }
113
+ }
114
+
115
+ try {
116
+ const response = await Promise.race([
117
+ fetch(fullUrl, fetchConfig),
118
+ this.timeoutPromise(this.timeout)
119
+ ])
120
+
121
+ // 应用响应拦截器
122
+ let finalResponse = response
123
+ this.responseInterceptors.forEach((interceptor) => {
124
+ finalResponse = interceptor(finalResponse)
125
+ })
126
+
127
+ if (!finalResponse.ok) {
128
+ throw new RequestError(
129
+ `HTTP ${finalResponse.status}: ${finalResponse.statusText}`,
130
+ finalResponse.status,
131
+ finalResponse.status
132
+ )
133
+ }
134
+
135
+ // 解析响应
136
+ const contentType = finalResponse.headers.get('content-type')
137
+ let data
138
+ if (contentType && contentType.includes('application/json')) {
139
+ data = await finalResponse.json()
140
+ } else {
141
+ data = await finalResponse.text()
142
+ }
143
+
144
+ return data
145
+ } catch (error) {
146
+ if (error instanceof RequestError) {
147
+ throw error
148
+ }
149
+ throw new RequestError(
150
+ error instanceof Error ? error.message : '网络错误',
151
+ 0, 0
152
+ )
153
+ }
154
+ }
155
+
156
+ // 便捷方法
157
+ get(url, params, config) {
158
+ return this.request(url, { ...config, method: 'GET', params })
159
+ }
160
+
161
+ post(url, data, config) {
162
+ return this.request(url, { ...config, method: 'POST', body: data })
163
+ }
164
+
165
+ put(url, data, config) {
166
+ return this.request(url, { ...config, method: 'PUT', body: data })
167
+ }
168
+
169
+ delete(url, config) {
170
+ return this.request(url, { ...config, method: 'DELETE' })
171
+ }
172
+ }
173
+
174
+ export default HttpClient
@@ -0,0 +1,159 @@
1
+ /**
2
+ * WebSocket 通信核心模块
3
+ */
4
+ export class ChatWebSocket {
5
+ constructor(userId, options = {}) {
6
+ this.userId = userId
7
+ this.wsUrl = options.wsUrl || ''
8
+ this.socket = null
9
+ this.reconnectAttempts = 0
10
+ this.maxReconnectAttempts = options.maxReconnectAttempts || 5
11
+ this.reconnectDelay = options.reconnectDelay || 3000
12
+ this.handlers = {
13
+ message: [],
14
+ open: [],
15
+ close: [],
16
+ error: []
17
+ }
18
+ this.isConnecting = false
19
+ this.manualClose = false
20
+ }
21
+
22
+ /**
23
+ * 连接 WebSocket
24
+ */
25
+ connect() {
26
+ if (this.isConnecting || this.isConnected()) return
27
+
28
+ this.isConnecting = true
29
+ this.manualClose = false
30
+
31
+ try {
32
+ this.socket = new WebSocket(this.wsUrl)
33
+
34
+ this.socket.onopen = () => {
35
+ console.log('[VueChatKit] WebSocket 连接成功')
36
+ this.isConnecting = false
37
+ this.reconnectAttempts = 0
38
+ this.emit('open')
39
+ }
40
+
41
+ this.socket.onmessage = (event) => {
42
+ try {
43
+ const data = event.data
44
+ this.emit('message', data)
45
+ } catch (e) {
46
+ console.error('[VueChatKit] WebSocket 消息解析失败', e)
47
+ }
48
+ }
49
+
50
+ this.socket.onclose = (event) => {
51
+ console.log('[VueChatKit] WebSocket 连接关闭', event.code)
52
+ this.isConnecting = false
53
+ this.emit('close', event)
54
+
55
+ if (!this.manualClose && event.code !== 1000) {
56
+ this.reconnect()
57
+ }
58
+ }
59
+
60
+ this.socket.onerror = (error) => {
61
+ console.error('[VueChatKit] WebSocket 连接错误', error)
62
+ this.isConnecting = false
63
+ this.emit('error', error)
64
+ }
65
+ } catch (error) {
66
+ console.error('[VueChatKit] WebSocket 创建连接失败', error)
67
+ this.isConnecting = false
68
+ }
69
+ }
70
+
71
+ /**
72
+ * 发送消息
73
+ */
74
+ send(to, message, type = 'text', fileUrl = '', fileName = '', fileSize = 0) {
75
+ if (this.isConnected()) {
76
+ const data = JSON.stringify({
77
+ to,
78
+ msg: message,
79
+ type,
80
+ fileUrl,
81
+ fileName,
82
+ fileSize
83
+ })
84
+ this.socket.send(data)
85
+ return true
86
+ }
87
+ console.warn('[VueChatKit] WebSocket 连接未建立,无法发送消息')
88
+ return false
89
+ }
90
+
91
+ /**
92
+ * 注册事件监听
93
+ */
94
+ on(event, handler) {
95
+ if (this.handlers[event]) {
96
+ this.handlers[event].push(handler)
97
+ }
98
+ }
99
+
100
+ /**
101
+ * 移除事件监听
102
+ */
103
+ off(event, handler) {
104
+ if (this.handlers[event]) {
105
+ const index = this.handlers[event].indexOf(handler)
106
+ if (index > -1) {
107
+ this.handlers[event].splice(index, 1)
108
+ }
109
+ }
110
+ }
111
+
112
+ /**
113
+ * 触发事件
114
+ */
115
+ emit(event, ...args) {
116
+ if (this.handlers[event]) {
117
+ this.handlers[event].forEach(fn => fn(...args))
118
+ }
119
+ }
120
+
121
+ /**
122
+ * 重连
123
+ */
124
+ reconnect() {
125
+ if (this.reconnectAttempts < this.maxReconnectAttempts) {
126
+ this.reconnectAttempts++
127
+ console.log(`[VueChatKit] 尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`)
128
+ setTimeout(() => this.connect(), this.reconnectDelay)
129
+ } else {
130
+ console.error('[VueChatKit] 重连次数已达上限')
131
+ }
132
+ }
133
+
134
+ /**
135
+ * 关闭连接
136
+ */
137
+ close() {
138
+ this.manualClose = true
139
+ if (this.socket) {
140
+ this.socket.close()
141
+ this.socket = null
142
+ this.handlers = {
143
+ message: [],
144
+ open: [],
145
+ close: [],
146
+ error: []
147
+ }
148
+ }
149
+ }
150
+
151
+ /**
152
+ * 检查连接状态
153
+ */
154
+ isConnected() {
155
+ return this.socket && this.socket.readyState === WebSocket.OPEN
156
+ }
157
+ }
158
+
159
+ export default ChatWebSocket
package/src/index.js ADDED
@@ -0,0 +1,27 @@
1
+ // 导出组件
2
+ export { default as ChatWindow } from './components/ChatWindow.vue'
3
+ export { default as AvatarCrop } from './components/AvatarCrop.vue'
4
+
5
+ // 导出 composables
6
+ export { useChat } from './composables/useChat.js'
7
+
8
+ // 导出配置
9
+ export { createChatConfig } from './config/index.js'
10
+
11
+ // 导出核心模块
12
+ export { ChatWebSocket } from './core/websocket.js'
13
+ export { ChatApi } from './core/api.js'
14
+ export { HttpClient } from './core/request.js'
15
+
16
+ // 导出类型(如果使用 TypeScript 的话)
17
+ // export * from './types/index.d.ts'
18
+
19
+ // 安装函数,用于 Vue 插件安装
20
+ export const VueChatKit = {
21
+ install(app) {
22
+ app.component('ChatWindow', ChatWindow)
23
+ app.component('AvatarCrop', AvatarCrop)
24
+ }
25
+ }
26
+
27
+ export default VueChatKit