/** * 找对象页 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 = { 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 }); }, 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(); }, });