238 lines
6.8 KiB
JavaScript
238 lines
6.8 KiB
JavaScript
/**
|
||
* 找对象页 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();
|
||
},
|
||
});
|