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

238 lines
6.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 找对象页 U19-U20(重构:用 ui-user-card + matchStore.like + recommendationService.discover)
*
* 不变量(AGENTS.md):
* - 第 2 条:Required 不可自动放宽——Discover 搜索仅透传筛选条件到后端,
* 由后端按 PartnerPreference 做硬约束过滤,前端不做任何"放宽再查"。
* - 第 4 条:Block 即时禁止互动——本页 Like 后由 matchStore 从列表移除。
* - 第 6 条:单向意向不泄露——Pass/Like 未 Mutual 时对方不收到通知。
* - 第 9 条:PostgreSQL 是 Source of Truth——搜索结果不在 Store 持久化,
* 列表状态为页面局部状态(BaseStore 原则 2)。
*/
const { matchStore, appStore, authStore } = require('../../stores');
const { recommendationService } = require('../../services');
const { to } = require('../../constants');
const DEFAULT_AGE_MIN = 18;
const DEFAULT_AGE_MAX = 60;
const PAGE_SIZE = 20;
Page({
data: {
users: [],
loading: false,
loadingMore: false,
cursor: null,
hasMore: true,
// 搜索栏关键词(编辑中)
keywordInput: '',
showFilter: false,
// 临时筛选(编辑中)
tempRegion: '',
tempAgeMin: DEFAULT_AGE_MIN,
tempAgeMax: DEFAULT_AGE_MAX,
// 已应用筛选
applied: { keyword: '', region: '', ageMin: DEFAULT_AGE_MIN, ageMax: DEFAULT_AGE_MAX },
hasActiveFilter: false,
regions: ['丹阳', '镇江', '南京'],
actionLoadingUserId: '',
emptyActions: [
{ text: '清空筛选', event: 'reset', variant: 'primary' },
{ text: '去今日推荐', event: 'home', variant: 'secondary' },
],
},
onLoad() {
if (!authStore.state.isLoggedIn) return;
this.fetchDiscover(true);
},
/** 拉取搜索结果(首页) */
async fetchDiscover(first) {
if (this.data.loading) return;
this.setData({ loading: true });
try {
const result = await recommendationService.discover(this._buildParams());
const items = result.items || [];
this.setData({
users: first ? items : [...this.data.users, ...items],
cursor: result.nextCursor || null,
hasMore: !!result.nextCursor,
loading: false,
});
} catch (err) {
this.setData({ loading: false });
appStore.toastError(err);
}
},
/** 加载更多 */
async loadMore() {
if (this.data.loadingMore || !this.data.hasMore || !this.data.cursor) return;
this.setData({ loadingMore: true });
try {
const result = await recommendationService.discover({
...this._buildParams(),
cursor: this.data.cursor,
});
const items = result.items || [];
this.setData({
users: [...this.data.users, ...items],
cursor: result.nextCursor || null,
hasMore: !!result.nextCursor,
});
} catch (err) {
appStore.toastError(err);
} finally {
this.setData({ loadingMore: false });
}
},
/** 构造请求参数(仅透传,后端守 Required 硬约束) */
_buildParams() {
const { applied } = this.data;
const params = { pageSize: PAGE_SIZE };
if (applied.keyword) params.keyword = applied.keyword;
if (applied.region) params.region = applied.region;
if (applied.ageMin !== DEFAULT_AGE_MIN) params.ageMin = applied.ageMin;
if (applied.ageMax !== DEFAULT_AGE_MAX) params.ageMax = applied.ageMax;
return params;
},
// ============ 搜索栏 ============
onKeywordInput(e) {
this.setData({ keywordInput: e.detail.value });
},
/** 关键词搜索(键盘确认或点搜索图标) */
onSearch() {
const keyword = (this.data.keywordInput || '').trim();
const applied = { ...this.data.applied, keyword };
this.setData({
applied,
hasActiveFilter: this._hasActiveFilter(applied),
});
this.fetchDiscover(true);
},
// ============ 筛选面板 ============
toggleFilter() {
this.setData({ showFilter: !this.data.showFilter });
},
onRegionTap(e) {
const val = e.currentTarget.dataset.val;
this.setData({ tempRegion: this.data.tempRegion === val ? '' : val });
},
onMinAgeChange(e) {
let min = e.detail.value;
if (min > this.data.tempAgeMax) min = this.data.tempAgeMax;
this.setData({ tempAgeMin: min });
},
onMaxAgeChange(e) {
let max = e.detail.value;
if (max < this.data.tempAgeMin) max = this.data.tempAgeMin;
this.setData({ tempAgeMax: max });
},
onApplyFilter() {
const { tempRegion, tempAgeMin, tempAgeMax, keywordInput } = this.data;
const applied = {
keyword: (keywordInput || '').trim(),
region: tempRegion,
ageMin: tempAgeMin,
ageMax: tempAgeMax,
};
this.setData({
applied,
hasActiveFilter: this._hasActiveFilter(applied),
showFilter: false,
});
this.fetchDiscover(true);
},
onResetFilter() {
const applied = { keyword: '', region: '', ageMin: DEFAULT_AGE_MIN, ageMax: DEFAULT_AGE_MAX };
this.setData({
keywordInput: '',
tempRegion: '',
tempAgeMin: DEFAULT_AGE_MIN,
tempAgeMax: DEFAULT_AGE_MAX,
applied,
hasActiveFilter: false,
showFilter: false,
});
this.fetchDiscover(true);
},
_hasActiveFilter(applied) {
return !!(
applied.keyword ||
applied.region ||
applied.ageMin !== DEFAULT_AGE_MIN ||
applied.ageMax !== DEFAULT_AGE_MAX
);
},
onEmptyAction(e) {
const { event } = e.detail || {};
if (event === 'reset') {
this.onResetFilter();
} else if (event === 'home') {
wx.switchTab({ url: '/pages/index/index' });
}
},
// ============ 卡片操作 ============
/** 卡片点击:查看资料 */
onViewDetail(e) {
const { userId } = e.detail || {};
if (!userId) return;
wx.navigateTo({ url: to.detail(userId) });
},
/** 喜欢(actions slot 按钮,catchtap 阻止冒泡到卡片) */
async onLike(e) {
const userId = e.currentTarget.dataset.userId;
if (!userId || this.data.actionLoadingUserId) return;
this.setData({ actionLoadingUserId: userId });
try {
const result = await matchStore.like(userId);
// 从列表移除(无论是否 Mutual,已做出判断)
this.setData({ users: this.data.users.filter((u) => u.userId !== userId) });
if (result && result.mutualMatch) {
appStore.toastSuccess('双向喜欢!可以开始聊天了');
if (result.matchedConversationId) {
wx.navigateTo({ url: to.chat(result.matchedConversationId) });
}
} else {
appStore.toastSuccess('已表达喜欢');
}
} catch (err) {
appStore.toastError(err);
} finally {
this.setData({ actionLoadingUserId: '' });
}
},
// ============ 列表交互 ============
onPullDownRefresh() {
this.fetchDiscover(true).finally(() => wx.stopPullDownRefresh());
},
onReachBottom() {
this.loadMore();
},
});