Files
danyang-match/miniapp/pages/index/index.js
T

173 lines
5.6 KiB
JavaScript
Raw Normal View History

2026-09-09 22:05:00 +08:00
/**
* 首页 - 每日推荐(重构:用 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 });
}
},
});