diff --git a/common/api.js b/common/api.js
index 990695f..692999f 100644
--- a/common/api.js
+++ b/common/api.js
@@ -32,8 +32,17 @@ function httpRequest(url, params) {
success: (res) => {
const code = res.statusCode
if (code === 200 || code === 201 || code === 204) {
+ // 业务级错误:HTTP 200 但 fail:true
+ if (res.data && res.data.fail === true) {
+ const bizMsg = res.data.msg || '操作失败'
+ if (!silent) {
+ uni.showToast({ title: bizMsg, icon: 'none', duration: 2500 })
+ }
+ reject({ msg: bizMsg, code })
+ return
+ }
resolve(res.data)
- } else if (code === 401 || code === 418) {
+ } else if (code === 401 ) {
// token/refresh_token 过期或无效,清除登录态
uni.removeStorageSync('auth_token')
uni.removeStorageSync('refresh_token')
@@ -76,8 +85,8 @@ function uploadRequest(url, params) {
uni.uploadFile({
url: url,
- filePath: params.data.fileName || params.data.filePath,
- name: params.data.name || 'file',
+ filePath: params.data.image || params.data.fileName || params.data.filePath,
+ name: params.data.name || 'image',
header: headers,
formData: params.data.formData || {},
success: (uploadRes) => {
@@ -156,137 +165,146 @@ export async function refreshAuthToken() {
}
// ==========================================
-// 酒友圈 API(当前返回 Mock 数据,后端就绪后切换为 SDK 调用)
+// 酒友圈 API(基于 HaveADrink SDK 真实接口)
// ==========================================
-import { MOCK_FEEDS, MOCK_COMMENTS, MOCK_FRIENDS, MOCK_FRIEND_REQUESTS, MOCK_EVENTS, MOCK_CONVERSATIONS, MOCK_MESSAGES } from './mock-data'
-/** 模拟网络延迟 */
-function mockDelay(data, ms = 300) {
- return new Promise(resolve => setTimeout(() => resolve({ data }), ms))
-}
-
-/** 获取动态信息流 */
-export function GetCircleFeeds({ page = 1, pageSize = 10 } = {}) {
- // TODO: 后端就绪后替换为 client.GetCircleFeeds(...)
- const start = (page - 1) * pageSize
- const list = MOCK_FEEDS.slice(start, start + pageSize)
- return mockDelay({ list, total: MOCK_FEEDS.length, hasMore: start + pageSize < MOCK_FEEDS.length })
+/** 获取动态信息流(游标分页) */
+export async function GetCircleFeeds({ lastId = 0, pageSize = 10 } = {}) {
+ const res = await client.GetFeeds({ lastId, pageSize })
+ return { data: res }
}
/** 点赞动态 */
-export function LikeFeed({ feedId }) {
- return mockDelay({ success: true })
+export async function LikeFeed({ feedId }) {
+ const res = await client.LikeFeed({ id: feedId })
+ return { data: res }
}
/** 取消点赞 */
-export function UnlikeFeed({ feedId }) {
- return mockDelay({ success: true })
+export async function UnlikeFeed({ feedId }) {
+ const res = await client.UnlikeFeed({ id: feedId })
+ return { data: res }
}
/** 获取动态评论 */
-export function GetFeedComments({ feedId }) {
- const list = MOCK_COMMENTS[feedId] || []
- return mockDelay({ list })
+export async function GetFeedComments({ feedId }) {
+ const res = await client.GetFeedComments({ id: feedId })
+ return { data: res }
}
/** 发表评论 */
-export function AddComment({ feedId, content }) {
- return mockDelay({ success: true, comment: { id: 'c_' + Date.now(), content, time: '刚刚' } })
+export async function AddComment({ feedId, content }) {
+ const res = await client.AddComment({ id: feedId, content })
+ return { data: res }
}
/** 发布动态 */
-export function PublishFeed({ text, images, recordId, visibility }) {
- return mockDelay({ success: true, feedId: 'feed_' + Date.now() })
+export async function PublishFeed({ text, images, recordId, visibility }) {
+ const res = await client.PublishFeed({ text, images, recordId, visibility })
+ return { data: res }
}
/** 获取酒友列表 */
-export function GetFriends({ keyword = '' } = {}) {
- const list = keyword
- ? MOCK_FRIENDS.filter(f => f.nickname.includes(keyword))
- : MOCK_FRIENDS
- return mockDelay({ list })
+export async function GetFriends({ keyword = '' } = {}) {
+ const res = await client.GetFriends({ keyword })
+ return { data: res }
}
/** 获取好友请求 */
-export function GetFriendRequests() {
- return mockDelay({ list: MOCK_FRIEND_REQUESTS })
+export async function GetFriendRequests() {
+ const res = await client.GetFriendRequests({})
+ return { data: res }
}
/** 发送好友请求 */
-export function SendFriendRequest({ userId }) {
- return mockDelay({ success: true })
+export async function SendFriendRequest({ userId, message = '' }) {
+ const res = await client.SendFriendRequest({ userId, message })
+ return { data: res }
}
/** 接受好友请求 */
-export function AcceptFriendRequest({ requestId }) {
- return mockDelay({ success: true })
+export async function AcceptFriendRequest({ requestId }) {
+ const res = await client.AcceptFriendRequest({ id: requestId })
+ return { data: res }
}
/** 删除酒友 */
-export function RemoveFriend({ userId }) {
- return mockDelay({ success: true })
+export async function RemoveFriend({ userId }) {
+ const res = await client.RemoveFriend({ userId })
+ return { data: res }
}
/** 获取酒局列表 */
-export function GetEvents({ status = '' } = {}) {
- const list = status ? MOCK_EVENTS.filter(e => e.status === status) : MOCK_EVENTS
- return mockDelay({ list })
+export async function GetEvents({ status = '' } = {}) {
+ const res = await client.GetEvents({ status })
+ return { data: res }
}
/** 获取酒局详情 */
-export function GetEventDetail({ eventId }) {
- const evt = MOCK_EVENTS.find(e => e.id === eventId) || MOCK_EVENTS[0]
- return mockDelay(evt)
+export async function GetEventDetail({ eventId }) {
+ const res = await client.GetEventDetail({ id: eventId })
+ return { data: res }
}
/** 发起酒局 */
-export function CreateEvent({ title, time, location, maxPeople, note }) {
- return mockDelay({ success: true, eventId: 'evt_' + Date.now() })
+export async function CreateEvent({ title, time, location, maxPeople, note, latitude, longitude }) {
+ const geo = (latitude != null && longitude != null) ? { latitude, longitude } : undefined
+ const res = await client.CreateEvent({ title, time, location, maxPeople, geo, note })
+ return { data: res }
}
/** 报名酒局 */
-export function JoinEvent({ eventId }) {
- return mockDelay({ success: true })
+export async function JoinEvent({ eventId }) {
+ const res = await client.JoinEvent({ id: eventId })
+ return { data: res }
}
/** 取消报名 */
-export function QuitEvent({ eventId }) {
- return mockDelay({ success: true })
+export async function QuitEvent({ eventId }) {
+ const res = await client.QuitEvent({ id: eventId })
+ return { data: res }
}
/** 酒局签到 */
-export function CheckInEvent({ eventId }) {
- return mockDelay({ success: true })
+export async function CheckInEvent({ eventId }) {
+ const res = await client.CheckInEvent({ id: eventId })
+ return { data: res }
}
// ==========================================
-// 私信 API(当前返回 Mock 数据,后端就绪后切换为 SDK 调用)
+// 私信 API(基于 HaveADrink SDK 真实接口)
// ==========================================
/** 获取会话列表 */
-export function GetConversations() {
- // TODO: 后端就绪后替换为 client.GetConversations(...)
- return mockDelay({ list: MOCK_CONVERSATIONS })
+export async function GetConversations() {
+ const res = await client.GetConversations({})
+ return { data: res }
}
-/** 获取历史消息 */
-export function GetMessages({ conversationId, page = 1, pageSize = 20 } = {}) {
- // TODO: 后端就绪后替换为 client.GetMessages(...)
- const all = MOCK_MESSAGES[conversationId] || []
- const start = (page - 1) * pageSize
- const list = all.slice(start, start + pageSize)
- return mockDelay({ list, hasMore: start + pageSize < all.length })
+/** 获取历史消息(游标分页) */
+export async function GetMessages({ conversationId, lastID = 0, pageSize = 20 } = {}) {
+ const res = await client.GetMessages({ conversationId, lastID, pageSize })
+ return { data: res }
}
/** 标记会话已读 */
-export function MarkRead({ conversationId }) {
- // TODO: 后端就绪后替换为 client.MarkRead(...)
- return mockDelay({ success: true })
+export async function MarkRead({ conversationId }) {
+ const res = await client.MarkRead({ id: conversationId })
+ return { data: res }
}
/** 获取未读消息总数 */
-export function GetUnreadCount() {
- // TODO: 后端就绪后替换为 client.GetUnreadCount(...)
- const total = MOCK_CONVERSATIONS.reduce((sum, c) => sum + c.unread, 0)
- return mockDelay({ total })
+export async function GetUnreadCount() {
+ const res = await client.GetUnreadCount({})
+ return { data: res }
+}
+
+// ==========================================
+// 文件上传 API
+// ==========================================
+
+/** 上传图片(返回 url) */
+export async function UploadImage({ filePath }) {
+ const res = await client.UploadImage({ image: filePath })
+ return { data: res }
}
diff --git a/common/websocket.js b/common/websocket.js
index ec73149..bd936e8 100644
--- a/common/websocket.js
+++ b/common/websocket.js
@@ -1,9 +1,29 @@
/* 碰盏日记 - WebSocket 管理器
* 单例模式,负责私信实时通信
+ * 协议格式与 HaveADrink SDK ChatWebSocket 一致:{ mesgType, data }
* 支持心跳、自动重连、消息队列
*/
-const WS_HOST = 'wss://dev.wash-painting.cn/ws'
+const WS_HOST = 'wss://dev.wash-painting.cn/api/have_a_drink/v1/chat/chat'
+
+// 客户端消息类型(与 SDK ClientMesgType 枚举值一致)
+const ClientMesgType = {
+ Chat: 'chat',
+ Typing: 'typing',
+ Read: 'read',
+ Ping: 'ping'
+}
+
+// 服务端消息类型(与 SDK ServerMesgType 枚举值一致)
+const ServerMesgType = {
+ Chat: 'chat',
+ Typing: 'typing',
+ Read: 'read',
+ Ack: 'ack',
+ Pong: 'pong',
+ Connected: 'connected',
+ Kicked: 'kicked'
+}
class WebSocketManager {
constructor() {
@@ -21,6 +41,7 @@ class WebSocketManager {
this.heartbeatWait = 60000 // 60s 无 pong 则重连
this.manualClose = false
this.token = ''
+ this.userId = '' // 连接成功后服务端返回的用户ID
}
/**
@@ -37,11 +58,14 @@ class WebSocketManager {
this.manualClose = false
this.isConnecting = true
- const url = `${WS_HOST}?token=${this.token}`
- console.log('[WS] 正在连接...', url)
+ const url = WS_HOST
+ console.log('[WS] 正在连接...')
this.socketTask = uni.connectSocket({
url,
+ header: {
+ 'Authorization': `Bearer ${this.token}`
+ },
complete: () => {}
})
@@ -95,17 +119,17 @@ class WebSocketManager {
}
/**
- * 发送消息
- * @param {string} type - 消息类型: chat | typing | read | ping
+ * 发送消息(与 SDK writeClient* 方法格式一致)
+ * @param {string} mesgType - 消息类型: chat | typing | read | ping
* @param {object} data - 消息数据
*/
- send(type, data = {}) {
- const payload = JSON.stringify({ type, data, ts: Date.now() })
+ send(mesgType, data = {}) {
+ const payload = JSON.stringify({ mesgType, data })
if (this.isConnected && this.socketTask) {
this.socketTask.send({ data: payload })
} else {
- // 断线暂存队列
- if (type !== 'ping') {
+ // 断线暂存队列(ping 不暂存)
+ if (mesgType !== ClientMesgType.Ping) {
this.messageQueue.push(payload)
}
// 尝试重连
@@ -117,7 +141,7 @@ class WebSocketManager {
/**
* 订阅事件
- * @param {string} event - message | typing | read | ack | connect | disconnect
+ * @param {string} event - message | typing | read | ack | connected | kicked | connect | disconnect
* @param {function} callback
*/
on(event, callback) {
@@ -143,27 +167,38 @@ class WebSocketManager {
// ========== 内部方法 ==========
- /** 处理收到的消息 */
+ /** 处理收到的消息(SDK ServerMesgType 协议) */
_handleMessage(msg) {
- const { type, data } = msg
- switch (type) {
- case 'pong':
- this._onPong()
- break
- case 'chat':
+ const { mesgType, data } = msg
+ switch (mesgType) {
+ case ServerMesgType.Chat:
this._emit('message', data)
break
- case 'typing':
+ case ServerMesgType.Typing:
this._emit('typing', data)
break
- case 'read':
+ case ServerMesgType.Read:
this._emit('read', data)
break
- case 'ack':
+ case ServerMesgType.Ack:
this._emit('ack', data)
break
+ case ServerMesgType.Pong:
+ this._onPong()
+ break
+ case ServerMesgType.Connected:
+ // 服务端验证 token 成功后推送,携带 userId
+ this.userId = data && data.userId ? data.userId : ''
+ console.log('[WS] 服务端确认连接, userId:', this.userId)
+ this._emit('connected', data)
+ break
+ case ServerMesgType.Kicked:
+ console.warn('[WS] 被踢下线')
+ this._emit('kicked', data)
+ this.disconnect()
+ break
default:
- console.log('[WS] 未知消息类型', type)
+ console.log('[WS] 未知消息类型', mesgType)
}
}
@@ -181,7 +216,7 @@ class WebSocketManager {
_startHeartbeat() {
this._stopHeartbeat()
this.heartbeatTimer = setInterval(() => {
- this.send('ping')
+ this.send(ClientMesgType.Ping, {})
// 设置超时检测
this.heartbeatTimeout = setTimeout(() => {
console.warn('[WS] 心跳超时,触发重连')
diff --git a/components/EventCard.vue b/components/EventCard.vue
index 5a63155..093353e 100644
--- a/components/EventCard.vue
+++ b/components/EventCard.vue
@@ -49,8 +49,9 @@ export default {
emits: ['item-click'],
computed: {
statusLabel() {
- const map = { open: '报名中', ongoing: '进行中', ended: '已结束' }
- return map[this.event.status] || '报名中'
+ const val = String(this.event.status).toLowerCase()
+ const map = { open: '报名中', upcoming: '待开始', ongoing: '进行中', ended: '已结束' }
+ return map[val] || '报名中'
}
}
}
@@ -80,6 +81,9 @@ export default {
&.status-open {
background: rgba(94,198,160,0.12);
}
+ &.status-upcoming {
+ background: rgba(139,133,184,0.12);
+ }
&.status-ongoing {
background: rgba(232,168,56,0.12);
}
@@ -93,6 +97,7 @@ export default {
font-weight: $fw-medium;
.status-open & { color: $mint; }
+ .status-upcoming & { color: $lavender; }
.status-ongoing & { color: $amber; }
.status-ended & { color: $text-tertiary; }
}
diff --git a/components/FeedCard.vue b/components/FeedCard.vue
index 833e148..ef3e87b 100644
--- a/components/FeedCard.vue
+++ b/components/FeedCard.vue
@@ -73,8 +73,15 @@ export default {
getCatIcon,
isIconPath,
feelingLabel(f) {
- const map = { sober: '😌 清醒', buzzed: '😊 微醺', tipsy: '😄 小醉', drunk: '🥴 喝多了' }
- return map[f] || '🍻 喝了点'
+ const val = String(f).toLowerCase()
+ const map = {
+ tipsy: '😌 微醺',
+ buzzed: '😊 到位',
+ drunk: '🥴 醉了',
+ blackout: '😵 断片',
+ sober: '😌 清醒'
+ }
+ return map[val] || '🍻 喝了点'
}
}
}
diff --git a/manifest.json b/manifest.json
index 34a3d2d..581b39e 100644
--- a/manifest.json
+++ b/manifest.json
@@ -46,6 +46,7 @@
"desc" : "用于选择酒局地点和导航"
}
},
+ "requiredPrivateInfos" : ["chooseLocation", "getLocation"],
"optimization" : {
"subPackages" : true
}
diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json
index f930d21..1c06d3e 100644
--- a/node_modules/.package-lock.json
+++ b/node_modules/.package-lock.json
@@ -4,9 +4,9 @@
"requires": true,
"packages": {
"node_modules/HaveADrink": {
- "version": "1.0.4",
- "resolved": "https://npm.wash-painting.cn/HaveADrink/-/HaveADrink-1.0.4.tgz",
- "integrity": "sha512-kSSpTZrQGjRueftSAopurZcwXGMS3oKQ0mvP3/sePJh7odO5ev9FT31isXX0LLmL1HojzB4nrIeDb/PTdGhYuA==",
+ "version": "1.0.8",
+ "resolved": "https://npm.wash-painting.cn/HaveADrink/-/HaveADrink-1.0.8.tgz",
+ "integrity": "sha512-YOaqEZg+T1MKz1OtRJc09niyxEwm+52g91Yj0L2aGEy6bK6pTLJYlmREdneIStiniUGHVjL2/U5Qk57lTBAuFw==",
"license": "ISC"
}
}
diff --git a/node_modules/HaveADrink/README.md b/node_modules/HaveADrink/README.md
index d0744e1..fbd048a 100644
--- a/node_modules/HaveADrink/README.md
+++ b/node_modules/HaveADrink/README.md
@@ -14,7 +14,7 @@
* 6. 社交模块 - 朋友圈动态、点赞
-**版本:** v1.0.4
+**版本:** v1.0.8
## 安装
@@ -350,58 +350,6 @@ const req = new GetFeedReq()
client.GetFeed(req).then(...).catch(...)
```
-### 点赞
-
-
-
-POST `/api/have_a_drink/v1/communication/records/like`
-
-#### 请求参数
-|名称|类型|校验规则|说明|
-|:-|:-|:-|:-|
-|id|`string\|number`|required| 记录ID|
-
-
-
-
-#### 返回值
-|名称|类型|说明|
-|:-|:-:|:-|
-
-
-
-
-```javascript
-const req = new LikeRecordReq()
-client.LikeRecord(req).then(...).catch(...)
-```
-
-### 取消点赞
-
-
-
-POST `/api/have_a_drink/v1/communication/records/unlike`
-
-#### 请求参数
-|名称|类型|校验规则|说明|
-|:-|:-|:-|:-|
-|id|`string\|number`|required| 记录ID|
-
-
-
-
-#### 返回值
-|名称|类型|说明|
-|:-|:-:|:-|
-
-
-
-
-```javascript
-const req = new LikeRecordReq()
-client.UnlikeRecord(req).then(...).catch(...)
-```
-
### 上传照片
@@ -479,7 +427,7 @@ client.UploadPhotos(req).then(...).catch(...)
**DrinkItem**
|名称|类型|校验规则|说明|
|:-|:-|:-|:-|
-|category|`Category`|required| 酒类分类ID|
+|category|`Category`|| 酒类分类ID|
|brand|`string`|required| 品牌名称|
|product|`string`|| 产品名|
|amount|`number`|required,gt=0| 饮用量数值|
@@ -532,7 +480,7 @@ client.UploadPhotos(req).then(...).catch(...)
**FoodInfo**
|名称|类型|校验规则|说明|
|:-|:-|:-|:-|
-|category|`FoodCategory`|required| 配餐分类ID|
+|category|`FoodCategory`|| 配餐分类ID|
|name|`string`|| 具体菜名|
@@ -1193,3 +1141,809 @@ client.UpdatePreferences(req).then(...).catch(...)
const req = new UploadImageReq()
client.UploadImage(req).then(...).catch(...)
```
+
+### 动态信息流
+
+
+
+GET `/api/have_a_drink/v1/moments/feeds`
+
+#### 请求参数
+|名称|类型|校验规则|说明|
+|:-|:-|:-|:-|
+|lastId|`string\|number`|| 最后一条动态ID,用于分页|
+|pageSize|`number`|| 每页数量|
+
+
+
+
+#### 返回值
+|名称|类型|说明|
+|:-|:-:|:-|
+|list|`Array`| 动态列表
|
+|hasMore|`boolean`| 是否还有更多动态
|
+
+
+
+**Feed**
+|名称|类型|说明|
+|:-|:-|:-|
+|id|`string\|number`| 动态ID|
+|userId|`string\|number`| 用户ID|
+|nickname|`string`| 昵称|
+|avatar|`string`| 头像|
+|time|`string`| 发布时间|
+|text|`string`| 动态文本|
+|drinks|`Array`| 酒水标签|
+|feeling|`Feeling`| 自我感觉|
+|images|`Array`| 图片列表|
+|likes|`number`| 点赞数|
+|liked|`boolean`| 是否点赞|
+|comments|`number`| 评论数|
+
+
+**DrinkTag**
+|名称|类型|说明|
+|:-|:-|:-|
+|category|`Category`| 酒类分类|
+|name|`string`| 酒类名称|
+|amount|`number`| 酒类数量|
+
+
+**Feeling**
+|名称|值|类型|说明|
+|:-|:-:|:-|:-|
+|Tipsy|"tipsy"|`Feeling`|微醺|
+|Buzzed|"buzzed"|`Feeling`|到位|
+|Drunk|"drunk"|`Feeling`|醉了|
+|Blackout|"blackout"|`Feeling`|断片|
+
+
+
+```javascript
+const req = new GetFeedsReq()
+client.GetFeeds(req).then(...).catch(...)
+```
+
+### 发布动态
+
+
+
+POST `/api/have_a_drink/v1/moments/feeds`
+
+#### 请求参数
+|名称|类型|校验规则|说明|
+|:-|:-|:-|:-|
+|text|`string`|| 动态文本|
+|images|`Array`|| 图片列表|
+|recordId|`string\|number`|| 关联的记录ID|
+|visibility|`Visibility`|公开: Visibility.Public = "public"
仅好友: Visibility.Friends = "friends"
仅自己: Visibility.Private = "private"| 可见性|
+
+
+
+
+#### 返回值
+|名称|类型|说明|
+|:-|:-:|:-|
+|success|`boolean`| 是否成功
|
+|feedId|`string\|number`| 动态ID
|
+
+
+
+
+```javascript
+const req = new PublishFeedReq()
+client.PublishFeed(req).then(...).catch(...)
+```
+
+### 点赞
+
+
+
+POST `/api/have_a_drink/v1/moments/feeds/like`
+
+#### 请求参数
+|名称|类型|校验规则|说明|
+|:-|:-|:-|:-|
+|id|`string\|number`|| 动态ID|
+
+
+
+
+#### 返回值
+|名称|类型|说明|
+|:-|:-:|:-|
+|ok|`boolean`| 是否成功
|
+
+
+
+
+```javascript
+const req = new LikeFeedReq()
+client.LikeFeed(req).then(...).catch(...)
+```
+
+### 取消点赞
+
+
+
+POST `/api/have_a_drink/v1/moments/feeds/unlike`
+
+#### 请求参数
+|名称|类型|校验规则|说明|
+|:-|:-|:-|:-|
+|id|`string\|number`|| 动态ID|
+
+
+
+
+#### 返回值
+|名称|类型|说明|
+|:-|:-:|:-|
+|ok|`boolean`| 是否成功
|
+
+
+
+
+```javascript
+const req = new UnlikeFeedReq()
+client.UnlikeFeed(req).then(...).catch(...)
+```
+
+### 获取评论
+
+
+
+GET `/api/have_a_drink/v1/moments/feeds/comments`
+
+#### 请求参数
+|名称|类型|校验规则|说明|
+|:-|:-|:-|:-|
+|id|`string\|number`|| 动态ID|
+
+
+
+
+#### 返回值
+|名称|类型|说明|
+|:-|:-:|:-|
+|list|`Array`| 评论列表
|
+
+
+
+**Comment**
+|名称|类型|说明|
+|:-|:-|:-|
+|id|`string\|number`| 评论ID|
+|userId|`string\|number`| 用户ID|
+|nickname|`string`| 昵称|
+|avatar|`string`| 头像|
+|content|`string`| 评论内容|
+|time|`string`| 评论时间|
+
+
+
+```javascript
+const req = new GetFeedCommentsReq()
+client.GetFeedComments(req).then(...).catch(...)
+```
+
+### 发表评论
+
+
+
+POST `/api/have_a_drink/v1/moments/feeds/comments`
+
+#### 请求参数
+|名称|类型|校验规则|说明|
+|:-|:-|:-|:-|
+|id|`string\|number`|| 动态ID|
+|content|`string`|| 评论内容|
+
+
+
+
+#### 返回值
+|名称|类型|说明|
+|:-|:-:|:-|
+|id|`string\|number`| 评论ID
|
+|userId|`string\|number`| 用户ID
|
+|nickname|`string`| 昵称
|
+|avatar|`string`| 头像
|
+|content|`string`| 评论内容
|
+|time|`string`| 评论时间
|
+
+
+
+
+```javascript
+const req = new AddCommentFullReq()
+client.AddComment(req).then(...).catch(...)
+```
+
+### 酒友管理
+
+
+
+GET `/api/have_a_drink/v1/friends/friends`
+
+#### 请求参数
+|名称|类型|校验规则|说明|
+|:-|:-|:-|:-|
+|keyword|`string`|| 搜索关键词|
+
+
+
+
+#### 返回值
+|名称|类型|说明|
+|:-|:-:|:-|
+|list|`Array`| 分页数据
|
+
+
+
+**Friend**
+|名称|类型|说明|
+|:-|:-|:-|
+|id|`string\|number`| 酒友ID|
+|nickname|`string`| 昵称|
+|avatar|`string`| 头像|
+|lastDrink|`string\|number`| 最近一次喝的酒|
+|lastDrinkTime|`string`| 最近一次喝的酒时间|
+|online|`boolean`| 是否在线|
+
+
+
+```javascript
+const req = new GetFriendsReq()
+client.GetFriends(req).then(...).catch(...)
+```
+
+### 好友请求管理
+
+
+
+GET `/api/have_a_drink/v1/friends/friend-requests`
+
+#### 请求参数
+|名称|类型|校验规则|说明|
+|:-|:-|:-|:-|
+
+
+
+
+#### 返回值
+|名称|类型|说明|
+|:-|:-:|:-|
+|list|`Array`| 分页数据
|
+
+
+
+**FriendRequest**
+|名称|类型|说明|
+|:-|:-|:-|
+|id|`string\|number`| 好友请求ID|
+|userId|`string\|number`| 酒友ID|
+|nickname|`string`| 昵称|
+|avatar|`string`| 头像|
+|message|`string`| 消息|
+|time|`string`| 创建时间|
+
+
+
+```javascript
+const req = new GetFriendRequestsReq()
+client.GetFriendRequests(req).then(...).catch(...)
+```
+
+### 发送好友请求
+
+
+
+POST `/api/have_a_drink/v1/friends/friend-requests`
+
+#### 请求参数
+|名称|类型|校验规则|说明|
+|:-|:-|:-|:-|
+|userId|`string\|number`|| 酒友ID|
+|message|`string`|| 消息|
+
+
+
+
+#### 返回值
+|名称|类型|说明|
+|:-|:-:|:-|
+
+
+
+
+```javascript
+const req = new SendFriendRequestReq()
+client.SendFriendRequest(req).then(...).catch(...)
+```
+
+### 接受好友请求
+
+
+
+POST `/api/have_a_drink/v1/friends/friend-requests/accept`
+
+#### 请求参数
+|名称|类型|校验规则|说明|
+|:-|:-|:-|:-|
+|id|`string\|number`|| 好友请求ID|
+
+
+
+
+#### 返回值
+|名称|类型|说明|
+|:-|:-:|:-|
+
+
+
+
+```javascript
+const req = new AcceptFriendRequestReq()
+client.AcceptFriendRequest(req).then(...).catch(...)
+```
+
+### 删除酒友
+
+
+
+DELETE `/api/have_a_drink/v1/friends/friends`
+
+#### 请求参数
+|名称|类型|校验规则|说明|
+|:-|:-|:-|:-|
+|userId|`string\|number`|| 酒友ID|
+
+
+
+
+#### 返回值
+|名称|类型|说明|
+|:-|:-:|:-|
+
+
+
+
+```javascript
+const req = new RemoveFriendReq()
+client.RemoveFriend(req).then(...).catch(...)
+```
+
+### 约酒活动
+
+
+
+GET `/api/have_a_drink/v1/events/events`
+
+#### 请求参数
+|名称|类型|校验规则|说明|
+|:-|:-|:-|:-|
+|status|`EventStatus`|开放报名: EventStatus.Open = 1
待开始: EventStatus.Upcoming = 2
进行中: EventStatus.Ongoing = 3
已结束: EventStatus.Ended = 4| 状态筛选|
+
+
+
+
+#### 返回值
+|名称|类型|说明|
+|:-|:-:|:-|
+|list|`Array`| 分页数据
|
+
+
+
+**Event**
+|名称|类型|说明|
+|:-|:-|:-|
+|id|`string\|number`| 酒局ID|
+|title|`string`| 酒局标题|
+|organizer|`User`| 酒局组织者|
+|time|`string`| 酒局时间|
+|location|`string`| 酒局地点|
+|maxPeople|`number`| 最大人数|
+|joined|`number`| 已报名人数|
+|participants|`Array`| 已报名用户|
+|status|`EventStatus`| 酒局状态|
+|note|`string`| 酒局备注|
+|isJoined|`boolean`| 是否已报名|
+|isOrganizer|`boolean`| 是否为组织者|
+
+
+**User**
+|名称|类型|说明|
+|:-|:-|:-|
+|id|`string\|number`| 用户ID|
+|nickname|`string`| 用户昵称|
+|avatar|`string`| 头像URL|
+|joinedAt|`string`| 加入时间|
+|preferences|`UserPreferences`| 用户偏好|
+
+
+**UserPreferences**
+|名称|类型|说明|
+|:-|:-|:-|
+|favoriteCategories|`Array`| 偏好酒类ID列表|
+|frequency|`Frequency`| 饮酒频率|
+
+
+**Frequency**
+|名称|值|类型|说明|
+|:-|:-:|:-|:-|
+|Rarely|"rarely"|`Frequency`|很少|
+|Sometimes|"sometimes"|`Frequency`|有时|
+|Often|"often"|`Frequency`|经常|
+
+
+**EventStatus**
+|名称|值|类型|说明|
+|:-|:-:|:-|:-|
+|Open|1|`EventStatus`|开放报名|
+|Upcoming|2|`EventStatus`|待开始|
+|Ongoing|3|`EventStatus`|进行中|
+|Ended|4|`EventStatus`|已结束|
+
+
+
+```javascript
+const req = new GetEventsReq()
+client.GetEvents(req).then(...).catch(...)
+```
+
+### 获取酒局详情
+
+
+
+GET `/api/have_a_drink/v1/events/events/detail`
+
+#### 请求参数
+|名称|类型|校验规则|说明|
+|:-|:-|:-|:-|
+|id|`string\|number`|| 酒局ID|
+
+
+
+
+#### 返回值
+|名称|类型|说明|
+|:-|:-:|:-|
+|id|`string\|number`| 酒局ID
|
+|title|`string`| 酒局标题
|
+|organizer|`User`| 酒局组织者
|
+|time|`string`| 酒局时间
|
+|location|`string`| 酒局地点
|
+|maxPeople|`number`| 最大人数
|
+|joined|`number`| 已报名人数
|
+|participants|`Array`| 已报名用户
|
+|status|`EventStatus`| 酒局状态
开放报名: EventStatus.Open = 1
待开始: EventStatus.Upcoming = 2
进行中: EventStatus.Ongoing = 3
已结束: EventStatus.Ended = 4|
+|note|`string`| 酒局备注
|
+|isJoined|`boolean`| 是否已报名
|
+|isOrganizer|`boolean`| 是否为组织者
|
+
+
+
+**User**
+|名称|类型|说明|
+|:-|:-|:-|
+|id|`string\|number`| 用户ID|
+|nickname|`string`| 用户昵称|
+|avatar|`string`| 头像URL|
+|joinedAt|`string`| 加入时间|
+|preferences|`UserPreferences`| 用户偏好|
+
+
+**UserPreferences**
+|名称|类型|说明|
+|:-|:-|:-|
+|favoriteCategories|`Array`| 偏好酒类ID列表|
+|frequency|`Frequency`| 饮酒频率|
+
+
+
+```javascript
+const req = new GetEventDetailReq()
+client.GetEventDetail(req).then(...).catch(...)
+```
+
+### 发起酒局
+
+
+
+POST `/api/have_a_drink/v1/events/events`
+
+#### 请求参数
+|名称|类型|校验规则|说明|
+|:-|:-|:-|:-|
+|title|`string`|| 酒局标题|
+|time|`string`|| 酒局时间|
+|location|`string`|| 酒局地点|
+|maxPeople|`number`|| 最大人数|
+|geo|`GeoPoint`|| 坐标信息|
+|note|`string`|| 酒局备注|
+
+
+
+**GeoPoint**
+|名称|类型|校验规则|说明|
+|:-|:-|:-|:-|
+|latitude|`number`|| 纬度|
+|longitude|`number`|| 经度|
+
+
+
+#### 返回值
+|名称|类型|说明|
+|:-|:-:|:-|
+|eventId|`string\|number`| 酒局ID
|
+
+
+
+
+```javascript
+const req = new CreateEventReq()
+client.CreateEvent(req).then(...).catch(...)
+```
+
+### 报名酒局
+
+
+
+POST `/api/have_a_drink/v1/events/events/join`
+
+#### 请求参数
+|名称|类型|校验规则|说明|
+|:-|:-|:-|:-|
+|id|`string\|number`|| 酒局ID|
+
+
+
+
+#### 返回值
+|名称|类型|说明|
+|:-|:-:|:-|
+|ok|`boolean`| 是否成功
|
+
+
+
+
+```javascript
+const req = new JoinEventReq()
+client.JoinEvent(req).then(...).catch(...)
+```
+
+### 取消报名酒局
+
+
+
+POST `/api/have_a_drink/v1/events/events/quit`
+
+#### 请求参数
+|名称|类型|校验规则|说明|
+|:-|:-|:-|:-|
+|id|`string\|number`|| 酒局ID|
+
+
+
+
+#### 返回值
+|名称|类型|说明|
+|:-|:-:|:-|
+|ok|`boolean`| 是否成功
|
+
+
+
+
+```javascript
+const req = new QuitEventReq()
+client.QuitEvent(req).then(...).catch(...)
+```
+
+### 签到酒局
+
+
+
+POST `/api/have_a_drink/v1/events/events/checkin`
+
+#### 请求参数
+|名称|类型|校验规则|说明|
+|:-|:-|:-|:-|
+|id|`string\|number`|| 酒局ID|
+
+
+
+
+#### 返回值
+|名称|类型|说明|
+|:-|:-:|:-|
+|ok|`boolean`| 是否成功
|
+
+
+
+
+```javascript
+const req = new CheckInEventReq()
+client.CheckInEvent(req).then(...).catch(...)
+```
+
+### 获取会话列表
+
+
+
+GET `/api/have_a_drink/v1/chat/conversations`
+
+#### 请求参数
+|名称|类型|校验规则|说明|
+|:-|:-|:-|:-|
+
+
+
+
+#### 返回值
+|名称|类型|说明|
+|:-|:-:|:-|
+|list|`Array`| 会话列表
|
+
+
+
+**Conversation**
+|名称|类型|说明|
+|:-|:-|:-|
+|id|`string`| 会话ID|
+|friendId|`string\|number`| 对方用户ID|
+|nickname|`string`| 对方昵称|
+|avatar|`string`| 对方头像URL(可为空)|
+|lastMessage|`string`| 最后一条消息摘要|
+|lastTime|`string`| 最后消息时间(ISO 8601)|
+|unread|`number`| 未读消息数|
+|online|`boolean`| 对方是否在线|
+
+
+
+```javascript
+const req = new GetConversationsReq()
+client.GetConversations(req).then(...).catch(...)
+```
+
+### 获取历史消息
+
+
+
+GET `/api/have_a_drink/v1/chat/messages`
+
+#### 请求参数
+|名称|类型|校验规则|说明|
+|:-|:-|:-|:-|
+|conversationId|`string`|required| 会话ID|
+|lastID|`string\|number`|| 游标分页|
+|pageSize|`number`|gt=0,lte=100| 每页数量,默认20|
+
+
+
+
+#### 返回值
+|名称|类型|说明|
+|:-|:-:|:-|
+|list|`Array`| 消息列表
|
+|hasMore|`boolean`| 是否还有更多消息
|
+
+
+
+**Message**
+|名称|类型|说明|
+|:-|:-|:-|
+|id|`string\|number`| 消息ID|
+|conversationId|`string`| 会话ID|
+|senderId|`string\|number`| 发送者ID|
+|receiverId|`string\|number`| 接收者ID|
+|type|`MessageType`| 消息类型|
+|content|`string`| 消息内容|
+|timestamp|`number`| 发送时间(毫秒时间戳)|
+|status|`MessageStatus`| 消息状态|
+
+
+**MessageType**
+|名称|值|类型|说明|
+|:-|:-:|:-|:-|
+|Text|"text"|`MessageType`|文本消息|
+|Image|"image"|`MessageType`|图片消息|
+
+
+**MessageStatus**
+|名称|值|类型|说明|
+|:-|:-:|:-|:-|
+|Sending|"sending"|`MessageStatus`|发送中|
+|Sent|"sent"|`MessageStatus`|已发送|
+|Delivered|"delivered"|`MessageStatus`|已送达|
+|Read|"read"|`MessageStatus`|已读|
+
+
+
+```javascript
+const req = new GetMessagesReq()
+client.GetMessages(req).then(...).catch(...)
+```
+
+### 标记会话已读
+
+
+
+POST `/api/have_a_drink/v1/chat/conversations/read`
+
+#### 请求参数
+|名称|类型|校验规则|说明|
+|:-|:-|:-|:-|
+|id|`string`|required| 会话ID(路径参数)|
+
+
+
+
+#### 返回值
+|名称|类型|说明|
+|:-|:-:|:-|
+|success|`boolean`| 是否成功
|
+
+
+
+
+```javascript
+const req = new MarkReadReq()
+client.MarkRead(req).then(...).catch(...)
+```
+
+### 获取未读消息总数
+
+
+
+GET `/api/have_a_drink/v1/chat/unread-count`
+
+#### 请求参数
+|名称|类型|校验规则|说明|
+|:-|:-|:-|:-|
+
+
+
+
+#### 返回值
+|名称|类型|说明|
+|:-|:-:|:-|
+|total|`number`| 未读总数
|
+
+
+
+
+```javascript
+const req = new GetUnreadCountReq()
+client.GetUnreadCount(req).then(...).catch(...)
+```
+
+### 注意:连接建立后,服务端会先验证 token,然后推送 connected 事件
+
+
+
+WS `/api/have_a_drink/v1/chat/chat`
+
+#### 请求参数
+|名称|类型|校验规则|说明|
+|:-|:-|:-|:-|
+|mesgType|`ClientMesgType`|发送消息: ClientMesgType.Chat = "chat"
输入状态: ClientMesgType.Typing = "typing"
已读回执: ClientMesgType.Read = "read"
心跳: ClientMesgType.Ping = "ping"| 消息类型|
+|data|`ClientMessage`|| 消息数据|
+
+
+
+
+#### 返回值
+|名称|类型|说明|
+|:-|:-:|:-|
+|mesgType|`ServerMesgType`| 消息类型
推送新消息: ServerMesgType.Chat = "chat"
对方输入状态: ServerMesgType.Typing = "typing"
对方已读回执: ServerMesgType.Read = "read"
消息送达确认: ServerMesgType.Ack = "ack"
心跳响应: ServerMesgType.Pong = "pong"
连接成功: ServerMesgType.Connected = "connected"
被踢下线: ServerMesgType.Kicked = "kicked"|
+|data|`ServerMessage`| 消息数据
|
+
+
+
+
+```javascript
+const req = new ChatWebSocket()
+client.ChatWebSocket(req).then(...).catch(...)
+```
diff --git a/node_modules/HaveADrink/index.d.ts b/node_modules/HaveADrink/index.d.ts
index 9cd4cd6..32e119d 100644
--- a/node_modules/HaveADrink/index.d.ts
+++ b/node_modules/HaveADrink/index.d.ts
@@ -263,6 +263,135 @@ export declare class Frequency extends Enum {
static getLabel(v: number|string): string;
}
+/**
+ * 酒局状态
+ */
+export declare class EventStatus extends Enum {
+ /**
+ * 开放报名
+ */
+ static Open: EventStatus;
+ /**
+ * 待开始
+ */
+ static Upcoming: EventStatus;
+ /**
+ * 进行中
+ */
+ static Ongoing: EventStatus;
+ /**
+ * 已结束
+ */
+ static Ended: EventStatus;
+
+ static entries(): Array