/** * 首页 - 每日推荐(重构:用 matchStore + recommend-card + appStore 统一编排) * * 关键不变量(AGENTS.md): * - 第 2 条:Required 不可自动放宽——推荐列表完全由后端按 PartnerPreference 过滤, * 前端只渲染后端返回的 recommendations,不做任何"放宽条件再查"的逻辑。 * - 第 5 条:红娘不可替用户操作——所有 Like/Pass 由当前用户触发。 * - 第 6 条:单向意向不泄露——likersCount 仅展示计数,不暴露具体身份。 */ const { matchStore, appStore, userStore, authStore, blockUser, likeUser } = require('../../stores'); const { ROUTE, to, RECOMMENDATION_STATE } = require('../../constants'); Page({ data: { greeting: '', userName: '', recommendations: [], recState: RECOMMENDATION_STATE.IDLE, likersCount: 0, actionLoadingUserId: '', // 正在操作的用户 id(Like/Pass 进行中) emptyActions: [ { text: '去「找对象」', event: 'discover', variant: 'primary' }, { text: '申请公益红娘', event: 'matchmaker', variant: 'secondary' }, ], }, onLoad() { this.setGreeting(); // 绑定 matchStore 关键字段到 page data this._unsubMatch = matchStore.bindToPage(this, { recommendations: 'recommendations', recState: 'recState', }); // 绑定 likersCount this._unsubMatchSub = matchStore.subscribe((state) => { if (state.likersCount !== this.data.likersCount) { this.setData({ likersCount: state.likersCount }); } }); // 绑定 userStore 用户名(用于问候语) this._unsubUser = userStore.subscribe((state) => { const name = state.profile?.displayName || ''; if (name !== this.data.userName) this.setData({ userName: name }); }); this.setData({ userName: userStore.state.profile?.displayName || '' }); }, onShow() { // 未登录则不请求推荐(bootstrap 已跳转登录页,此处为保险兜底) if (!authStore.state.isLoggedIn) return; // 每次显示刷新推荐(用户从聊天页返回后可能已 Like/Pass,需要新批次) this.loadRecommendations(); // 异步刷新 likersCount(Tab 徽标也会更新) matchStore.fetchLikersCount().catch(() => {}); }, onUnload() { this._unsubMatch && this._unsubMatch(); this._unsubMatchSub && this._unsubMatchSub(); this._unsubUser && this._unsubUser(); }, onPullDownRefresh() { this.loadRecommendations().finally(() => wx.stopPullDownRefresh()); }, onReachBottom() { // 加载更多推荐 matchStore.loadMoreRecommendations().catch(() => {}); }, setGreeting() { const hour = new Date().getHours(); let greeting = '早上好'; if (hour >= 12 && hour < 17) greeting = '下午好'; else if (hour >= 17) greeting = '晚上好'; this.setData({ greeting }); }, async loadRecommendations() { try { await matchStore.fetchRecommendations(); } catch (err) { appStore.toastError(err); } }, onViewDetail(e) { const { userId } = e.detail; wx.navigateTo({ url: to.detail(userId) }); }, async onLike(e) { const { userId } = e.detail; if (this.data.actionLoadingUserId) return; this.setData({ actionLoadingUserId: userId }); try { const result = await likeUser(userId); if (result && result.mutualMatch) { // 双向匹配达成:弹祝福 + 引导去聊天 appStore.toastSuccess('双向喜欢!可以开始聊天了'); setTimeout(() => { wx.showModal({ title: '互相喜欢', content: '你们已经互相喜欢,是否现在开始聊天?', confirmText: '开始聊天', cancelText: '稍后', confirmColor: '#E96B6B', success: (res) => { if (res.confirm && result.matchedConversationId) { wx.navigateTo({ url: to.chat(result.matchedConversationId) }); } }, }); }, 800); } else { appStore.toastSuccess('已表达喜欢'); } } catch (err) { appStore.toastError(err); } finally { this.setData({ actionLoadingUserId: '' }); } }, async onPass(e) { const { userId } = e.detail; if (this.data.actionLoadingUserId) return; this.setData({ actionLoadingUserId: userId }); try { await matchStore.pass(userId); // Pass 是单向意向,不打扰用户(AGENTS 第 6 条隐式) } catch (err) { // Pass 失败不提示,避免打扰 } finally { this.setData({ actionLoadingUserId: '' }); } }, async onBlock(e) { const { userId } = e.detail; const target = this.data.recommendations.find((r) => r.userId === userId); const name = target?.displayName || '该用户'; // 危险动作二次确认(AGENTS 第 4 条) const confirmed = await appStore.confirm({ title: '拉黑该用户', content: `拉黑后 ${name} 将无法与你互动,你也不会再收到对方的推荐与消息。此操作可由你随时解除。`, confirmText: '确认拉黑', danger: true, }); if (!confirmed) return; try { await blockUser(userId, '用户主动拉黑'); appStore.toastSuccess('已拉黑'); } catch (err) { appStore.toastError(err); } }, onViewLikers() { wx.navigateTo({ url: ROUTE.DETAIL.LIKERS }); }, onEmptyAction(e) { const { event } = e.detail; if (event === 'discover') { wx.switchTab({ url: ROUTE.TAB.DISCOVER }); } else if (event === 'matchmaker') { wx.navigateTo({ url: ROUTE.DETAIL.MATCHMAKER_APPLY }); } }, });