feat(sdk): 升级HaveADrink SDK并集成邀请系统

- 将HaveADrink依赖从1.0.8升级至1.0.12版本
- 集成邀请码功能,新增common/invite.js处理邀请链路
- 实现发送好友请求返回邀请码的新流程
- 添加删除动态功能,新增DeleteFeed接口
- 集成UniAppWebSocket适配器,重构WebSocket连接管理
- 优化好友请求支持关键词搜索功能
- 在FeedCard组件中添加删除按钮和分享按钮
- 更新SDK类型定义文件以匹配新接口规范
This commit is contained in:
cg
2026-08-04 23:32:45 +08:00
parent f988c8d868
commit 57eef74eb1
19 changed files with 1461 additions and 223 deletions
+224 -9
View File
@@ -41,8 +41,11 @@
v-for="feed in feeds"
:key="feed.id"
:feed="feed"
:can-delete="isMyFeed(feed)"
@like="handleLike"
@comment="goDetail"
@share="handleShare"
@delete="confirmDeleteFeed"
@item-click="goDetail"
/>
<view v-if="loading" class="circle-loading">
@@ -71,12 +74,23 @@
<text class="request-text">{{ friendRequests.length }} 条新的好友请求</text>
<text class="request-arrow"></text>
</view>
<!-- 酒友管理入口有好友时也可进入处理请求/删除酒友 -->
<view v-if="friends.length && !friendRequests.length" class="friends-entry" @click="goFriends">
<text class="friends-entry-icon">👥</text>
<text class="friends-entry-text">管理酒友 · 处理好友请求</text>
<text class="friends-entry-arrow"></text>
</view>
<FriendItem
v-for="f in friends"
:key="f.id"
:friend="f"
@item-click="goChat(f)"
/>
<!-- 有好友时也保留邀请入口直接触发带邀请码的转发 -->
<button v-if="friends.length" class="invite-btn" open-type="share" @click="prepareInviteShare">
<text class="invite-btn-icon">👥</text>
<text class="invite-btn-text">邀请更多酒友</text>
</button>
<EmptyState
v-if="!friends.length"
icon="👥"
@@ -120,8 +134,9 @@ import FeedCard from '../../components/FeedCard.vue'
import FriendItem from '../../components/FriendItem.vue'
import EventCard from '../../components/EventCard.vue'
import EmptyState from '../../components/EmptyState.vue'
import { GetCircleFeeds, LikeFeed, UnlikeFeed, GetFriends, GetFriendRequests, GetEvents, GetUnreadCount } from '../../common/api'
import { GetCircleFeeds, LikeFeed, UnlikeFeed, DeleteFeed, GetFriends, GetFriendRequests, GetEvents, GetUnreadCount } from '../../common/api'
import wsManager from '../../common/websocket'
import { getMyInviteCode } from '../../common/invite'
import themeMixin from '../../common/theme-mixin'
export default {
@@ -147,7 +162,15 @@ export default {
// 酒局
events: [],
// 私信未读
unreadTotal: 0
unreadTotal: 0,
// 当前分享的动态
shareFeed: null,
// 分享模式:feed(分享动态) / invite(邀请好友) / default
shareMode: 'default',
// 我的邀请码(分享时携带,一次性)
inviteCode: '',
// 当前登录用户ID(用于判断动态是否为自己发布)
myUserId: ''
}
},
onShow() {
@@ -155,16 +178,56 @@ export default {
this.loadFriends()
this.loadEvents()
this.loadUnread()
this._wsHandler = () => { this.unreadTotal++ }
wsManager.on('message', this._wsHandler)
this.bindWsEvents()
// 读取当前用户ID(删除自己的动态时用)
try {
const user = JSON.parse(uni.getStorageSync('user_info') || '{}')
this.myUserId = user.id !== undefined && user.id !== null ? String(user.id) : ''
} catch (e) { this.myUserId = '' }
// 预生成邀请码(仅首次/用完后生成,避免每次进页都创建无效邀请)
this.ensureInviteCode()
// 页面显示时确保 WS 已连接(登录后/断线后兼容)
wsManager.connect()
},
onHide() {
if (this._wsHandler) {
wsManager.off('message', this._wsHandler)
this._wsHandler = null
}
this.unbindWsEvents()
},
onUnload() {
this.unbindWsEvents()
},
methods: {
// === WebSocket 事件 ===
bindWsEvents() {
if (this._wsBound) return
this._wsBound = true
this._wsMsgHandler = this.onWsMessage
this._wsConnectHandler = this.onWsConnect
wsManager.on('message', this._wsMsgHandler)
wsManager.on('connect', this._wsConnectHandler)
},
unbindWsEvents() {
if (!this._wsBound) return
this._wsBound = false
if (this._wsMsgHandler) {
wsManager.off('message', this._wsMsgHandler)
this._wsMsgHandler = null
}
if (this._wsConnectHandler) {
wsManager.off('connect', this._wsConnectHandler)
this._wsConnectHandler = null
}
},
/** 收到新私信:未读角标实时 +1(自己发的/自己读的不计数) */
onWsMessage(data) {
if (!data) return
const senderId = data.senderId
if (senderId && wsManager.userId && String(senderId) === String(wsManager.userId)) return
this.unreadTotal++
},
/** (重)连成功后拉取真实未读数,修正断线期间的漏计/多计 */
onWsConnect() {
this.loadUnread()
},
switchTab(i) {
this.currentTab = i
},
@@ -218,9 +281,49 @@ export default {
}
} catch (e) { /* ignore */ }
},
/** 判断是否为自己发布的动态(仅自己可删) */
isMyFeed(feed) {
if (!this.myUserId) return false
return feed && feed.userId !== undefined && feed.userId !== null && String(feed.userId) === this.myUserId
},
/** 删除自己的动态(二次确认) */
confirmDeleteFeed(feed) {
uni.showModal({
title: '删除动态',
content: '确定要删除这条动态吗?删除后不可恢复',
confirmText: '删除',
confirmColor: '#FF6B6B',
success: async (res) => {
if (!res.confirm) return
try {
await DeleteFeed({ feedId: feed.id })
this.feeds = this.feeds.filter(f => String(f.id) !== String(feed.id))
uni.showToast({ title: '已删除', icon: 'none' })
} catch (e) {
// httpRequest 已全局弹错误提示(如非本人动态后端会拒绝)
}
}
})
},
goDetail(feed) {
uni.navigateTo({ url: `/pages/circle-detail/circle-detail?id=${feed.id}` })
},
/** 记录当前要分享的动态,供 onShareAppMessage 读取 */
handleShare(feed) {
this.shareMode = 'feed'
this.shareFeed = feed
},
/** 点击邀请按钮:标记本次分享为邀请模式 */
prepareInviteShare() {
this.shareMode = 'invite'
},
/** 确保邀请码已生成(仅缺失时拉取,避免频繁创建一次性邀请) */
ensureInviteCode() {
if (this.inviteCode) return
getMyInviteCode().then(code => {
if (code) this.inviteCode = code
})
},
goPublish() {
uni.navigateTo({ url: '/pages/circle-publish/circle-publish' })
},
@@ -255,7 +358,9 @@ export default {
async loadUnread() {
try {
const res = await GetUnreadCount()
this.unreadTotal = res.data.total
if (res.data && typeof res.data.total === 'number') {
this.unreadTotal = res.data.total
}
} catch (e) { /* ignore */ }
},
// === 酒局 ===
@@ -281,6 +386,49 @@ export default {
this.goPublish()
}
}
},
onShareAppMessage() {
// 邀请好友分享:链接携带一次性邀请码,好友点开后自动接受邀请
if (this.shareMode === 'invite') {
let user = {}
try {
user = JSON.parse(uni.getStorageSync('user_info') || '{}')
} catch (e) { /* ignore */ }
const parts = []
if (this.inviteCode) parts.push(`inviteCode=${encodeURIComponent(this.inviteCode)}`)
if (user.nickname) parts.push(`inviterNick=${encodeURIComponent(user.nickname)}`)
const query = parts.length ? `?${parts.join('&')}` : ''
const nick = user.nickname ? `${user.nickname}` : ''
// 本次分享已用掉当前邀请码,异步生成新码供下次使用
this.inviteCode = ''
this.ensureInviteCode()
this.shareMode = 'default'
return {
title: `${nick}邀请你成为酒友,一起记录饮酒生活 🍻`,
path: `/pages/index/index${query}`
}
}
const feed = this.shareFeed
if (this.shareMode === 'feed' && feed && feed.id) {
this.shareMode = 'default'
const text = (feed.text || '').replace(/\s+/g, ' ').trim()
const title = text
? `${feed.nickname || '酒友'}${text.length > 24 ? text.slice(0, 24) + '…' : text}`
: `${feed.nickname || '酒友'}的动态 - 碰盏日记`
return {
title,
path: `/pages/circle-detail/circle-detail?id=${feed.id}&nick=${encodeURIComponent(feed.nickname || '')}&text=${encodeURIComponent(text.slice(0, 60))}&time=${encodeURIComponent(feed.time || '')}`
}
}
return {
title: '酒友圈 - 碰盏日记',
path: '/pages/index/index'
}
},
onShareTimeline() {
return {
title: '酒友圈 - 碰盏日记'
}
}
}
</script>
@@ -443,6 +591,73 @@ export default {
opacity: 0.6;
}
/* 酒友管理入口 */
.friends-entry {
display: flex;
align-items: center;
gap: $sp-sm;
padding: $sp-md $sp-lg;
background: $bg-card;
border: 1rpx solid var(--border-faint, rgba(255,255,255,0.08));
border-radius: $radius-lg;
margin-bottom: $sp-md;
&:active {
background: $bg-card-alt;
}
}
.friends-entry-icon {
font-size: $fs-base;
}
.friends-entry-text {
flex: 1;
font-size: $fs-sm;
color: $text-secondary;
font-weight: $fw-medium;
}
.friends-entry-arrow {
font-size: $fs-xl;
color: $text-tertiary;
opacity: 0.6;
}
/* 邀请更多酒友按钮(重置 button 默认样式) */
.invite-btn {
display: flex;
align-items: center;
justify-content: center;
gap: $sp-sm;
width: 100%;
margin: $sp-md 0 0;
padding: $sp-lg 0;
background: rgba(232,168,56,0.08);
border: 1rpx dashed rgba(232,168,56,0.35);
border-radius: $radius-lg;
line-height: 1;
font-size: inherit;
&:active {
background: rgba(232,168,56,0.14);
}
&::after {
border: none;
}
}
.invite-btn-icon {
font-size: $fs-base;
}
.invite-btn-text {
font-size: $fs-sm;
color: $amber;
font-weight: $fw-bold;
}
/* FAB */
.fab {
position: fixed;