feat(sdk): 升级HaveADrink SDK并集成邀请系统
- 将HaveADrink依赖从1.0.8升级至1.0.12版本 - 集成邀请码功能,新增common/invite.js处理邀请链路 - 实现发送好友请求返回邀请码的新流程 - 添加删除动态功能,新增DeleteFeed接口 - 集成UniAppWebSocket适配器,重构WebSocket连接管理 - 优化好友请求支持关键词搜索功能 - 在FeedCard组件中添加删除按钮和分享按钮 - 更新SDK类型定义文件以匹配新接口规范
This commit is contained in:
+39
-12
@@ -178,20 +178,30 @@ export default {
|
||||
async initChat() {
|
||||
// 如果没有传入 conversationId,通过会话列表查找
|
||||
if (!this.conversationId && this.friendId) {
|
||||
try {
|
||||
const res = await GetConversations()
|
||||
const conv = (res.data.list || []).find(c => String(c.friendId) === String(this.friendId))
|
||||
if (conv) {
|
||||
this.conversationId = conv.id
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
await this.ensureConversationId()
|
||||
}
|
||||
this.loadMessages()
|
||||
this.markRead()
|
||||
this.bindWsEvents()
|
||||
},
|
||||
/** 从会话列表按 friendId 查找 conversationId(首次聊天时会话尚未创建,返回 false) */
|
||||
async ensureConversationId() {
|
||||
if (this.conversationId) return true
|
||||
if (!this.friendId) return false
|
||||
try {
|
||||
const res = await GetConversations()
|
||||
const conv = (res.data.list || []).find(c => String(c.friendId) === String(this.friendId))
|
||||
if (conv) {
|
||||
this.conversationId = conv.id
|
||||
return true
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
return false
|
||||
},
|
||||
// === 数据加载 ===
|
||||
async loadMessages() {
|
||||
// 无会话ID不发请求(首次聊天无历史消息,避免空参请求)
|
||||
if (!this.conversationId) return
|
||||
try {
|
||||
const res = await GetMessages({ conversationId: this.conversationId, lastID: this.lastID, pageSize: 20 })
|
||||
this.messages = res.data.list || []
|
||||
@@ -209,6 +219,8 @@ export default {
|
||||
},
|
||||
|
||||
async markRead() {
|
||||
// 无会话ID无需标记已读
|
||||
if (!this.conversationId) return
|
||||
try {
|
||||
await MarkRead({ conversationId: this.conversationId })
|
||||
// 通过 WebSocket 发送已读回执
|
||||
@@ -238,6 +250,10 @@ export default {
|
||||
onWsMessage(data) {
|
||||
// 只处理当前会话的消息
|
||||
if (String(data.senderId) !== String(this.friendId)) return
|
||||
// 首次会话:后端创建会话后消息携带 conversationId,回填本地
|
||||
if (!this.conversationId && data.conversationId) {
|
||||
this.conversationId = data.conversationId
|
||||
}
|
||||
const msg = {
|
||||
id: data.msgId ? String(data.msgId) : 'msg_' + Date.now(),
|
||||
conversationId: data.conversationId || this.conversationId,
|
||||
@@ -251,11 +267,13 @@ export default {
|
||||
this.messages.push(msg)
|
||||
this.peerTyping = false
|
||||
this.$nextTick(() => this.scrollToBottom())
|
||||
// 发送已读回执
|
||||
wsManager.send('read', {
|
||||
conversationId: this.conversationId,
|
||||
lastMsgId: msg.id
|
||||
})
|
||||
// 发送已读回执(需已有会话ID)
|
||||
if (this.conversationId) {
|
||||
wsManager.send('read', {
|
||||
conversationId: this.conversationId,
|
||||
lastMsgId: msg.id
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/** 对方正在输入 */
|
||||
@@ -270,6 +288,10 @@ export default {
|
||||
|
||||
/** 消息送达确认 */
|
||||
onWsAck(data) {
|
||||
// ack 若携带会话ID,回填本地(首次会话兼容)
|
||||
if (!this.conversationId && data.conversationId) {
|
||||
this.conversationId = data.conversationId
|
||||
}
|
||||
const msg = this.messages.find(m => m.id === data.clientMsgId || m.id === String(data.clientMsgId))
|
||||
if (msg) {
|
||||
msg.id = data.msgId ? String(data.msgId) : msg.id
|
||||
@@ -308,6 +330,11 @@ export default {
|
||||
msgType: 'text',
|
||||
clientMesgId: clientMsgId
|
||||
})
|
||||
|
||||
// 首次聊天无会话ID:后端收到首条消息后会创建会话,延迟查找回填
|
||||
if (!this.conversationId) {
|
||||
setTimeout(() => { this.ensureConversationId() }, 800)
|
||||
}
|
||||
},
|
||||
|
||||
/** 重发失败消息 */
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
<FeedCard
|
||||
v-if="feed"
|
||||
:feed="feed"
|
||||
:can-delete="isMyFeed(feed)"
|
||||
@like="handleLike"
|
||||
@share="handleShare"
|
||||
@delete="confirmDeleteFeed"
|
||||
/>
|
||||
|
||||
<!-- 评论区 -->
|
||||
@@ -62,7 +65,7 @@
|
||||
import FeedCard from '../../components/FeedCard.vue'
|
||||
import CommentItem from '../../components/CommentItem.vue'
|
||||
import EmptyState from '../../components/EmptyState.vue'
|
||||
import { GetFeedComments, AddComment, LikeFeed, UnlikeFeed } from '../../common/api'
|
||||
import { GetFeedComments, AddComment, LikeFeed, UnlikeFeed, DeleteFeed } from '../../common/api'
|
||||
import themeMixin from '../../common/theme-mixin'
|
||||
|
||||
export default {
|
||||
@@ -75,11 +78,16 @@ export default {
|
||||
feedId: '',
|
||||
feed: null,
|
||||
comments: [],
|
||||
commentText: ''
|
||||
commentText: '',
|
||||
// 当前分享的动态
|
||||
shareFeed: null,
|
||||
// 分享落地页携带的动态快照参数(好友打开分享链接时兼容展示)
|
||||
shareOptions: {}
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
this.feedId = options.id || ''
|
||||
this.shareOptions = options || {}
|
||||
this.loadFeed()
|
||||
this.loadComments()
|
||||
},
|
||||
@@ -88,10 +96,27 @@ export default {
|
||||
uni.navigateBack()
|
||||
},
|
||||
async loadFeed() {
|
||||
// 从 globalData 中查找对应动态
|
||||
// 优先从 globalData 中查找对应动态
|
||||
const app = getApp()
|
||||
const feeds = (app.globalData && app.globalData.circleFeeds) || []
|
||||
this.feed = feeds.find(f => String(f.id) === String(this.feedId)) || null
|
||||
const found = feeds.find(f => String(f.id) === String(this.feedId)) || null
|
||||
if (found) {
|
||||
this.feed = found
|
||||
return
|
||||
}
|
||||
// 分享落地页兼容:本地无缓存时用分享链接携带的快照参数展示
|
||||
const o = this.shareOptions
|
||||
if (o.nick || o.text) {
|
||||
this.feed = {
|
||||
id: this.feedId,
|
||||
nickname: decodeURIComponent(o.nick || ''),
|
||||
text: decodeURIComponent(o.text || ''),
|
||||
time: decodeURIComponent(o.time || ''),
|
||||
avatar: '',
|
||||
likes: 0,
|
||||
comments: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadComments() {
|
||||
try {
|
||||
@@ -109,6 +134,39 @@ export default {
|
||||
else await UnlikeFeed({ feedId: feed.id })
|
||||
} catch (e) { /* ignore */ }
|
||||
},
|
||||
/** 记录当前要分享的动态,供 onShareAppMessage 读取 */
|
||||
handleShare(feed) {
|
||||
this.shareFeed = feed
|
||||
},
|
||||
/** 判断是否为自己发布的动态(仅自己可删) */
|
||||
isMyFeed(feed) {
|
||||
let myId = ''
|
||||
try {
|
||||
const user = JSON.parse(uni.getStorageSync('user_info') || '{}')
|
||||
myId = user.id !== undefined && user.id !== null ? String(user.id) : ''
|
||||
} catch (e) { /* ignore */ }
|
||||
if (!myId) return false
|
||||
return feed && feed.userId !== undefined && feed.userId !== null && String(feed.userId) === myId
|
||||
},
|
||||
/** 删除自己的动态(二次确认),成功后返回列表 */
|
||||
confirmDeleteFeed(feed) {
|
||||
uni.showModal({
|
||||
title: '删除动态',
|
||||
content: '确定要删除这条动态吗?删除后不可恢复',
|
||||
confirmText: '删除',
|
||||
confirmColor: '#FF6B6B',
|
||||
success: async (res) => {
|
||||
if (!res.confirm) return
|
||||
try {
|
||||
await DeleteFeed({ feedId: feed.id })
|
||||
uni.showToast({ title: '已删除', icon: 'none' })
|
||||
setTimeout(() => uni.navigateBack(), 600)
|
||||
} catch (e) {
|
||||
// httpRequest 已全局弹错误提示(如非本人动态后端会拒绝)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
async submitComment() {
|
||||
const text = this.commentText.trim()
|
||||
if (!text) return
|
||||
@@ -128,6 +186,23 @@ export default {
|
||||
uni.showToast({ title: '发送失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
},
|
||||
onShareAppMessage() {
|
||||
const feed = this.shareFeed || this.feed
|
||||
if (feed && feed.id) {
|
||||
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'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
+224
-9
@@ -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;
|
||||
|
||||
@@ -64,6 +64,11 @@
|
||||
</view>
|
||||
</template>
|
||||
</FriendItem>
|
||||
<!-- 有好友时也保留邀请入口,直接触发带邀请码的转发 -->
|
||||
<button v-if="!keyword" class="invite-btn" open-type="share">
|
||||
<text class="invite-btn-icon">👥</text>
|
||||
<text class="invite-btn-text">邀请更多酒友</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
@@ -90,6 +95,7 @@
|
||||
import FriendItem from '../../components/FriendItem.vue'
|
||||
import EmptyState from '../../components/EmptyState.vue'
|
||||
import { GetFriends, GetFriendRequests, AcceptFriendRequest, RemoveFriend } from '../../common/api'
|
||||
import { savePendingInvite, handlePendingInvite, getMyInviteCode } from '../../common/invite'
|
||||
import themeMixin from '../../common/theme-mixin'
|
||||
|
||||
export default {
|
||||
@@ -102,13 +108,30 @@ export default {
|
||||
keyword: '',
|
||||
friends: [],
|
||||
requests: [],
|
||||
searchTimer: null
|
||||
searchTimer: null,
|
||||
// 我的邀请码(分享时携带,页面加载时预生成)
|
||||
inviteCode: ''
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
onLoad(options) {
|
||||
this.loadData()
|
||||
// 被分享进入本页时,若携带邀请码参数,继续处理邀请链路
|
||||
if (options && options.inviteCode) {
|
||||
savePendingInvite(options.inviteCode, options.inviterNick)
|
||||
if (uni.getStorageSync('is_logged_in') === 'true') {
|
||||
handlePendingInvite().then(() => this.loadData())
|
||||
}
|
||||
}
|
||||
// 预生成新的邀请码,供右上角分享使用(后端每次邀请都生成新码)
|
||||
this.refreshInviteCode()
|
||||
},
|
||||
methods: {
|
||||
/** 生成新的邀请码(每次分享后调用,保证下次分享用新码) */
|
||||
refreshInviteCode() {
|
||||
getMyInviteCode().then(code => {
|
||||
this.inviteCode = code
|
||||
})
|
||||
},
|
||||
goBack() {
|
||||
uni.navigateBack()
|
||||
},
|
||||
@@ -173,9 +196,21 @@ export default {
|
||||
}
|
||||
},
|
||||
onShareAppMessage() {
|
||||
// 分享链接携带当前邀请码(一次性),好友点开后接受邀请即可成为酒友
|
||||
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.refreshInviteCode()
|
||||
return {
|
||||
title: '来「碰盏日记」一起记录饮酒生活吧!',
|
||||
path: '/pages/index/index'
|
||||
title: `${nick}邀请你成为酒友,一起记录饮酒生活 🍻`,
|
||||
path: `/pages/index/index${query}`
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -375,4 +410,38 @@ export default {
|
||||
font-size: $fs-xs;
|
||||
color: $coral;
|
||||
}
|
||||
|
||||
/* 邀请更多酒友按钮(重置 button 默认样式) */
|
||||
.invite-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: $sp-sm;
|
||||
width: 100%;
|
||||
margin: $sp-lg 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;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -134,6 +134,7 @@ import client from '../../common/api'
|
||||
import { GetCalendarReq, DeleteRecordReq, CreateRecordReq, GetRecordDetailReq } from 'HaveADrink'
|
||||
import { getGreeting, formatDate, getWeekStart, getCatIcon, isIconPath } from '../../common/utils'
|
||||
import { FEELINGS, DRINK_CATEGORIES, DRINK_UNITS } from '../../common/constants'
|
||||
import { savePendingInvite, handlePendingInvite } from '../../common/invite'
|
||||
import themeMixin from '../../common/theme-mixin'
|
||||
|
||||
export default {
|
||||
@@ -170,6 +171,15 @@ export default {
|
||||
return this.selectedDate === formatDate(new Date(), 'YYYY-MM-DD')
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
// 分享邀请落地:携带 inviteCode 时暂存邀请,已登录则立即接受邀请
|
||||
if (options && options.inviteCode) {
|
||||
savePendingInvite(options.inviteCode, options.inviterNick)
|
||||
if (uni.getStorageSync('is_logged_in') === 'true') {
|
||||
handlePendingInvite()
|
||||
}
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
// 首次启动跳转引导页
|
||||
const isFirst = uni.getStorageSync('is_first_launch')
|
||||
|
||||
@@ -91,6 +91,8 @@
|
||||
<script>
|
||||
import themeMixin from '../../common/theme-mixin'
|
||||
import client, { saveAuthTokens } from '../../common/api'
|
||||
import { handlePendingInvite } from '../../common/invite'
|
||||
import wsManager from '../../common/websocket'
|
||||
|
||||
export default {
|
||||
mixins: [themeMixin],
|
||||
@@ -205,6 +207,15 @@ export default {
|
||||
finishLogin() {
|
||||
uni.setStorageSync('is_logged_in', 'true')
|
||||
uni.setStorageSync('is_first_launch', 'false')
|
||||
// 登录完成后用新 token 重建 WebSocket 连接
|
||||
// (启动时可能用旧 token 连接被拒,不重建会一直用旧 token 重试)
|
||||
const token = uni.getStorageSync('auth_token')
|
||||
if (token) {
|
||||
wsManager.disconnect()
|
||||
wsManager.connect(token)
|
||||
}
|
||||
// 登录完成后补发分享邀请的好友请求(若有暂存的邀请人)
|
||||
handlePendingInvite()
|
||||
uni.switchTab({ url: '/pages/index/index' })
|
||||
},
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 连续打卡卡片 -->
|
||||
<!-- 连续打卡卡片(含连续戒酒状态,暂时注释隐藏)
|
||||
<view class="streak-section">
|
||||
<view class="streak-card" :class="stats.streakType === 'drank' ? 'streak-drank' : 'streak-abstain'">
|
||||
<view class="streak-left">
|
||||
@@ -89,6 +89,7 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
-->
|
||||
|
||||
<!-- 饮酒人格卡片 -->
|
||||
<view class="persona-card">
|
||||
|
||||
Reference in New Issue
Block a user