feat(discover): rewrite filter UI to align with backend DiscoverController 5 params

- Top chips: 推荐(重置) / 丹阳本地(regionCode=321181) / 年龄 / 学历 / 婚姻
- Each chip has real active state bound to applied filter value
- Panel expanded to 4 sections: age slider + region + education + maritalStatus
- Param names fixed: minAge/maxAge/regionCode/educationCode/maritalStatus
- Data flow: panel temp -> onApplyFilter -> applied -> _buildParams -> API
- Panel opens with current applied values, closes with openFilterPanel/closeFilterPanel
This commit is contained in:
Codex
2026-09-14 11:09:23 +08:00
parent 0f06d82a92
commit 146ebab61d
2 changed files with 249 additions and 78 deletions
+155 -68
View File
@@ -1,10 +1,11 @@
/**
* 找对象页 U19-U20(TypeScript)
* 找对象页 — 筛选对齐后端 DiscoverController 5 个筛选参数:
* minAge, maxAge, regionCode, maritalStatus, educationCode
*
* 不变量(AGENTS.md):
* - 第 2 条:Required 不可自动放宽——Discover 搜索仅透传筛选条件到后端
* - 第 4 条:Block 即时禁止互动
* - 第 6 条:单向意向不泄露
* - Required 不可自动放宽——Discover 仅透传筛选条件到后端
* - Block 即时禁止互动
* - 单向意向不泄露
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { matchStore, appStore, authStore } from '../../stores';
@@ -14,6 +15,15 @@ import { recommendationService } from '../../services';
import { to } from '../../constants';
import type { RecommendationCandidate } from '../../types';
interface AppliedFilter {
keyword: string;
regionCode: string; // 后端参数名
minAge: number;
maxAge: number;
educationCode: string; // 后端参数名
maritalStatus: string; // 后端参数名
}
interface DiscoverData {
users: RecommendationCandidate[];
loading: boolean;
@@ -22,18 +32,22 @@ interface DiscoverData {
hasMore: boolean;
keywordInput: string;
showFilter: boolean;
tempRegion: string;
tempAgeMin: number;
tempAgeMax: number;
applied: { keyword: string; region: string; ageMin: number; ageMax: number };
// 面板临时态
tempRegionCode: string;
tempMinAge: number;
tempMaxAge: number;
tempEducationCode: string;
tempMaritalStatus: string;
applied: AppliedFilter;
hasActiveFilter: boolean;
regions: string[];
regions: Array<{ code: string; label: string }>;
educations: Array<{ code: string; label: string }>;
maritalStatuses: Array<{ code: string; label: string }>;
actionLoadingUserId: string;
emptyActions: Array<{ text: string; event: string; variant: string }>;
}
const DEFAULT_AGE_MIN = 18;
const DEFAULT_AGE_MAX = 60;
const DEFAULT_MIN_AGE = 18;
const DEFAULT_MAX_AGE = 60;
const PAGE_SIZE = 20;
Page({
@@ -45,18 +59,37 @@ Page({
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 },
tempRegionCode: '',
tempMinAge: DEFAULT_MIN_AGE,
tempMaxAge: DEFAULT_MAX_AGE,
tempEducationCode: '',
tempMaritalStatus: '',
applied: {
keyword: '',
regionCode: '',
minAge: DEFAULT_MIN_AGE,
maxAge: DEFAULT_MAX_AGE,
educationCode: '',
maritalStatus: '',
},
hasActiveFilter: false,
regions: ['丹阳', '镇江', '南京'],
actionLoadingUserId: '',
emptyActions: [
{ text: '清空筛选', event: 'reset', variant: 'primary' },
{ text: '去今日推荐', event: 'home', variant: 'secondary' },
regions: [
{ code: '321181', label: '丹阳' },
{ code: '321100', label: '镇江' },
{ code: '320100', label: '南京' },
],
},
educations: [
{ code: 'high_school', label: '高中及以下' },
{ code: 'college', label: '大专' },
{ code: 'bachelor', label: '本科' },
{ code: 'master', label: '硕士及以上' },
],
maritalStatuses: [
{ code: 'never_married', label: '未婚' },
{ code: 'divorced', label: '离异' },
],
actionLoadingUserId: '',
} as DiscoverData,
onLoad() {
if (!authStore.state.isLoggedIn) return;
@@ -67,7 +100,7 @@ Page({
if (this.data.loading) return;
this.setData({ loading: true });
try {
// @ts-ignore - recommendationService 接口待 TypeScript 化
// @ts-ignore - recommendationService 接口待 TS 化
const result = await recommendationService.discover(this._buildParams());
const items: RecommendationCandidate[] = (result?.items as RecommendationCandidate[]) || [];
this.setData({
@@ -104,16 +137,29 @@ Page({
}
},
/** 对齐后端 DiscoverController 5 个筛选参数 */
_buildParams() {
const { applied } = this.data;
const a = this.data.applied;
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;
if (a.keyword) params.keyword = a.keyword;
if (a.regionCode) params.regionCode = a.regionCode;
if (a.minAge !== DEFAULT_MIN_AGE) params.minAge = a.minAge;
if (a.maxAge !== DEFAULT_MAX_AGE) params.maxAge = a.maxAge;
if (a.educationCode) params.educationCode = a.educationCode;
if (a.maritalStatus) params.maritalStatus = a.maritalStatus;
return params;
},
_hasActiveFilter(applied: AppliedFilter) {
return !!(
applied.keyword || applied.regionCode ||
applied.minAge !== DEFAULT_MIN_AGE || applied.maxAge !== DEFAULT_MAX_AGE ||
applied.educationCode || applied.maritalStatus
);
},
// ========== 搜索框 ==========
onKeywordInput(e: any) {
this.setData({ keywordInput: e.detail.value });
},
@@ -125,68 +171,109 @@ Page({
this.fetchDiscover(true);
},
toggleFilter() {
this.setData({ showFilter: !this.data.showFilter });
},
// ========== 顶部 chip 点击(即时应用) ==========
onRegionTap(e: WechatMiniprogram.TouchEvent) {
const val = (e.currentTarget.dataset as any).val as string;
this.setData({ tempRegion: this.data.tempRegion === val ? '' : val });
},
/** 顶部"丹阳本地" 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 };
onTopRegionTap() {
// 丹阳本地 chip:regionCode=321181
const target = '321181';
const nextCode = this.data.applied.regionCode === target ? '' : target;
const applied = { ...this.data.applied, regionCode: nextCode };
this.setData({
tempRegion: nextRegion,
tempRegionCode: nextCode,
applied,
hasActiveFilter: this._hasActiveFilter(applied),
});
this.fetchDiscover(true);
},
/** 打开筛选面板,并同步当前 applied 状态到 temp */
openFilterPanel() {
const a = this.data.applied;
this.setData({
showFilter: true,
tempRegionCode: a.regionCode,
tempMinAge: a.minAge,
tempMaxAge: a.maxAge,
tempEducationCode: a.educationCode,
tempMaritalStatus: a.maritalStatus,
});
},
closeFilterPanel() {
this.setData({ showFilter: false });
},
// ========== 筛选面板内交互 ==========
onPanelRegionTap(e: WechatMiniprogram.TouchEvent) {
const code = (e.currentTarget.dataset as any).code as string;
this.setData({ tempRegionCode: this.data.tempRegionCode === code ? '' : code });
},
onPanelEducationTap(e: WechatMiniprogram.TouchEvent) {
const code = (e.currentTarget.dataset as any).code as string;
this.setData({ tempEducationCode: this.data.tempEducationCode === code ? '' : code });
},
onPanelMaritalTap(e: WechatMiniprogram.TouchEvent) {
const code = (e.currentTarget.dataset as any).code as string;
this.setData({ tempMaritalStatus: this.data.tempMaritalStatus === code ? '' : code });
},
onMinAgeChange(e: any) {
let min = Number((e as any).detail.value);
if (min > this.data.tempAgeMax) min = this.data.tempAgeMax;
this.setData({ tempAgeMin: min });
let min = Number(e.detail.value);
if (min > this.data.tempMaxAge) min = this.data.tempMaxAge;
this.setData({ tempMinAge: 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 });
let max = Number(e.detail.value);
if (max < this.data.tempMinAge) max = this.data.tempMinAge;
this.setData({ tempMaxAge: 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 };
const a = this.data.applied;
const applied: AppliedFilter = {
keyword: a.keyword,
regionCode: this.data.tempRegionCode,
minAge: this.data.tempMinAge,
maxAge: this.data.tempMaxAge,
educationCode: this.data.tempEducationCode,
maritalStatus: this.data.tempMaritalStatus,
};
this.setData({
keywordInput: '', tempRegion: '', tempAgeMin: DEFAULT_AGE_MIN, tempAgeMax: DEFAULT_AGE_MAX,
applied, hasActiveFilter: false, showFilter: false,
applied,
hasActiveFilter: this._hasActiveFilter(applied),
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
);
onResetFilter() {
const applied: AppliedFilter = {
keyword: '',
regionCode: '',
minAge: DEFAULT_MIN_AGE,
maxAge: DEFAULT_MAX_AGE,
educationCode: '',
maritalStatus: '',
};
this.setData({
keywordInput: '',
tempRegionCode: '',
tempMinAge: DEFAULT_MIN_AGE,
tempMaxAge: DEFAULT_MAX_AGE,
tempEducationCode: '',
tempMaritalStatus: '',
applied,
hasActiveFilter: false,
showFilter: false,
});
this.fetchDiscover(true);
},
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) || {};