2026-09-09 22:05:00 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 找对象页 U19-U20(TypeScript)
|
|
|
|
|
|
*
|
|
|
|
|
|
* 不变量(AGENTS.md):
|
|
|
|
|
|
* - 第 2 条:Required 不可自动放宽——Discover 搜索仅透传筛选条件到后端
|
|
|
|
|
|
* - 第 4 条:Block 即时禁止互动
|
|
|
|
|
|
* - 第 6 条:单向意向不泄露
|
|
|
|
|
|
*/
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
|
|
|
|
import { matchStore, appStore, authStore } from '../../stores';
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
|
|
|
|
import { recommendationService } from '../../services';
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
|
|
|
|
import { to } from '../../constants';
|
|
|
|
|
|
import type { RecommendationCandidate } from '../../types';
|
|
|
|
|
|
|
|
|
|
|
|
interface DiscoverData {
|
|
|
|
|
|
users: RecommendationCandidate[];
|
|
|
|
|
|
loading: boolean;
|
|
|
|
|
|
loadingMore: boolean;
|
|
|
|
|
|
cursor: string | null;
|
|
|
|
|
|
hasMore: boolean;
|
|
|
|
|
|
keywordInput: string;
|
|
|
|
|
|
showFilter: boolean;
|
|
|
|
|
|
tempRegion: string;
|
|
|
|
|
|
tempAgeMin: number;
|
|
|
|
|
|
tempAgeMax: number;
|
|
|
|
|
|
applied: { keyword: string; region: string; ageMin: number; ageMax: number };
|
|
|
|
|
|
hasActiveFilter: boolean;
|
|
|
|
|
|
regions: string[];
|
|
|
|
|
|
actionLoadingUserId: string;
|
|
|
|
|
|
emptyActions: Array<{ text: string; event: string; variant: string }>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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: boolean) {
|
|
|
|
|
|
if (this.data.loading) return;
|
|
|
|
|
|
this.setData({ loading: true });
|
|
|
|
|
|
try {
|
|
|
|
|
|
// @ts-ignore - recommendationService 接口待 TypeScript 化
|
|
|
|
|
|
const result = await recommendationService.discover(this._buildParams());
|
|
|
|
|
|
const items: RecommendationCandidate[] = (result?.items as RecommendationCandidate[]) || [];
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
users: first ? items : [...this.data.users, ...items],
|
|
|
|
|
|
cursor: result?.nextCursor || null,
|
|
|
|
|
|
hasMore: !!result?.nextCursor,
|
|
|
|
|
|
loading: false,
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (err: unknown) {
|
|
|
|
|
|
this.setData({ loading: false });
|
|
|
|
|
|
appStore.toastError(err instanceof Error ? err.message : '加载失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
async loadMore() {
|
|
|
|
|
|
if (this.data.loadingMore || !this.data.hasMore || !this.data.cursor) return;
|
|
|
|
|
|
this.setData({ loadingMore: true });
|
|
|
|
|
|
try {
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
|
const result = await recommendationService.discover({
|
|
|
|
|
|
...this._buildParams(),
|
|
|
|
|
|
cursor: this.data.cursor,
|
|
|
|
|
|
});
|
|
|
|
|
|
const items: RecommendationCandidate[] = (result?.items as RecommendationCandidate[]) || [];
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
users: [...this.data.users, ...items],
|
|
|
|
|
|
cursor: result?.nextCursor || null,
|
|
|
|
|
|
hasMore: !!result?.nextCursor,
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (err: unknown) {
|
|
|
|
|
|
appStore.toastError(err instanceof Error ? err.message : '加载失败');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.setData({ loadingMore: false });
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
_buildParams() {
|
|
|
|
|
|
const { applied } = this.data;
|
|
|
|
|
|
const params: Record<string, unknown> = { 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: any) {
|
|
|
|
|
|
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: WechatMiniprogram.TouchEvent) {
|
|
|
|
|
|
const val = (e.currentTarget.dataset as any).val as string;
|
|
|
|
|
|
this.setData({ tempRegion: this.data.tempRegion === val ? '' : val });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-09-14 10:59:43 +08:00
|
|
|
|
/** 顶部"丹阳本地" chip 专用:立即应用 region 筛选并刷新结果 */
|
|
|
|
|
|
onTopRegionTap(e: WechatMiniprogram.TouchEvent) {
|
|
|
|
|
|
const val = (e.currentTarget.dataset as any).val as string;
|
|
|
|
|
|
const nextRegion = this.data.applied.region === val ? '' : val;
|
|
|
|
|
|
const applied = { ...this.data.applied, region: nextRegion };
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
tempRegion: nextRegion,
|
|
|
|
|
|
applied,
|
|
|
|
|
|
hasActiveFilter: this._hasActiveFilter(applied),
|
|
|
|
|
|
});
|
|
|
|
|
|
this.fetchDiscover(true);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-09-09 22:05:00 +08:00
|
|
|
|
onMinAgeChange(e: any) {
|
|
|
|
|
|
let min = Number((e as any).detail.value);
|
|
|
|
|
|
if (min > this.data.tempAgeMax) min = this.data.tempAgeMax;
|
|
|
|
|
|
this.setData({ tempAgeMin: min });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onMaxAgeChange(e: any) {
|
|
|
|
|
|
let max = Number((e as any).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: { keyword: string; region: string; ageMin: number; ageMax: number }) {
|
|
|
|
|
|
return !!(
|
|
|
|
|
|
applied.keyword || applied.region ||
|
|
|
|
|
|
applied.ageMin !== DEFAULT_AGE_MIN || applied.ageMax !== DEFAULT_AGE_MAX
|
|
|
|
|
|
);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onEmptyAction(e: WechatMiniprogram.TouchEvent) {
|
|
|
|
|
|
const { event } = (e.detail as any) || {};
|
|
|
|
|
|
if (event === 'reset') this.onResetFilter();
|
|
|
|
|
|
else if (event === 'home') wx.switchTab({ url: '/pages/index/index' });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onViewDetail(e: WechatMiniprogram.TouchEvent) {
|
|
|
|
|
|
const { userId } = (e.detail as any) || {};
|
|
|
|
|
|
if (!userId) return;
|
|
|
|
|
|
wx.navigateTo({ url: to.detail(userId) });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
async onLike(e: WechatMiniprogram.TouchEvent) {
|
|
|
|
|
|
const userId = (e.currentTarget.dataset as any).userId as string | undefined;
|
|
|
|
|
|
if (!userId || this.data.actionLoadingUserId) return;
|
|
|
|
|
|
this.setData({ actionLoadingUserId: userId });
|
|
|
|
|
|
try {
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
|
const result = await matchStore.like(userId);
|
|
|
|
|
|
this.setData({ users: this.data.users.filter((u) => u.userId !== userId) });
|
|
|
|
|
|
if (result?.mutualMatch) {
|
|
|
|
|
|
appStore.toastSuccess('双向喜欢!可以开始聊天了');
|
|
|
|
|
|
if (result.matchedConversationId) {
|
|
|
|
|
|
wx.navigateTo({ url: to.chat(result.matchedConversationId) });
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
appStore.toastSuccess('已表达喜欢');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (err: unknown) {
|
|
|
|
|
|
appStore.toastError(err instanceof Error ? err.message : '操作失败');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.setData({ actionLoadingUserId: '' });
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onPullDownRefresh() {
|
|
|
|
|
|
this.fetchDiscover(true).finally(() => wx.stopPullDownRefresh());
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onReachBottom() {
|
|
|
|
|
|
this.loadMore();
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|