diff --git a/miniapp/pages/discover/discover.js b/miniapp/pages/discover/discover.js deleted file mode 100644 index efd585d..0000000 --- a/miniapp/pages/discover/discover.js +++ /dev/null @@ -1,237 +0,0 @@ -/** - * 找对象页 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(); - }, -}); diff --git a/miniapp/pages/index/index.js b/miniapp/pages/index/index.js deleted file mode 100644 index 7537d14..0000000 --- a/miniapp/pages/index/index.js +++ /dev/null @@ -1,129 +0,0 @@ -const { authStore, appStore } = require('../../stores') -const api = require('../../utils/api') - -const QUICK_ENTRIES = [ - { code: 'discover', label: '找对象', icon: '缘', tone: 'rose', targetPage: '/pages/discover/discover', tab: true, enabled: true }, - { code: 'matchmaker', label: '公益红娘', icon: '牵', tone: 'amber', targetPage: '/pages/matchmaker/matchmaker', enabled: true }, - { code: 'activity', label: '公益活动', icon: '聚', tone: 'green', targetPage: '/pages/activity/activity', tab: true, enabled: true }, - { code: 'counseling', label: '婚恋咨询', icon: '谈', tone: 'blue', targetPage: '/pages/counseling-intro/counseling-intro', enabled: true }, - { code: 'classroom', label: '婚恋课堂', icon: '学', tone: 'purple', targetPage: '/pages/content/content', enabled: true }, - { code: 'premarital', label: '婚前辅导', icon: '伴', tone: 'orange', targetPage: '/pages/premarital-intro/premarital-intro', enabled: true }, - { code: 'anti-fraud', label: '婚恋反诈', icon: '安', tone: 'indigo', targetPage: '/pages/anti-fraud/anti-fraud', enabled: true }, - { code: 'station', label: '善缘服务站', icon: '站', tone: 'teal', targetPage: '/pages/service-station/service-station', enabled: true }, -] - -function previewData() { - return { - userState: { verified: true, profileStatus: 'approved', localEligibilityStatus: 'verified', relationshipStatus: 'searching' }, - banners: [{ id: 'preview-banner', title: '让真诚的相遇,在丹阳发生', subtitle: '实名认证 · 人工审核 · 公益服务', buttonText: '了解平台', targetType: 'internal_page', targetValue: '/pages/about/about' }], - quickEntries: QUICK_ENTRIES, - recommendations: [{ userId: 'preview-user', displayName: '林女士', age: 29, heightCm: 165, regionName: '丹阳', occupationCode: '教育行业', verified: true, reviewed: true, recommendationLevel: 'high', recommendationReasons: ['双方年龄与生活规划较为契合', '都期待认真稳定的关系'], intro: '喜欢阅读和徒步,重视坦诚沟通。' }], - serviceRecommendations: [{ type: 'matchmaker', title: '需要更有温度的帮助?', description: '公益红娘会在尊重双方意愿的前提下提供人工推荐。', ctaText: '申请公益红娘' }], - activities: [{ id: 'preview-activity', title: '“初秋相遇”青年公益交友活动', startAt: '2026-09-20T14:00:00+08:00', locationName: '善缘丹阳公益服务站', typeLabel: '公益交友' }], - serviceLocation: { id: 'preview-station', name: '善缘丹阳公益服务站', shortName: '线下咨询与活动服务阵地', services: ['资料协助', '服务预约', '活动咨询'] }, - articles: [{ id: 'preview-article', title: '第一次见面,怎样自然地开始交流', categoryName: '沟通成长', readTime: '5分钟' }, { id: 'preview-article-2', title: '建立一段健康关系的三个边界', categoryName: '关系成长', readTime: '6分钟' }], - safetyNotice: { text: '涉及借款、投资和个人隐私时请保持谨慎,发现异常及时举报。', ctaText: '查看反诈指南' }, - } -} - -Page({ - data: { - unreadCount: 2, - loading: true, - loadError: false, - previewMode: false, - userState: null, - isOnboarding: false, - isVerified: false, - isPaused: false, - isSuccessful: false, - onboardingMessage: '', - onboardingActions: [], - statusChecks: [], - banners: [], - quickEntries: QUICK_ENTRIES, - recommendations: [], - recState: 'loading', - actionLoadingUserId: '', - serviceRecommendations: [], - activities: [], - serviceLocation: null, - articles: [], - safetyNotice: { text: '涉及借款、投资和个人隐私时请保持谨慎。', ctaText: '查看反诈指南' }, - emptyActions: [{ text: '调整择偶条件', event: 'preference', variant: 'secondary' }, { text: '申请公益红娘', event: 'matchmaker', variant: 'primary' }], - }, - onLoad() { this.loadHomeData() }, - onPullDownRefresh() { this.loadHomeData().finally(() => wx.stopPullDownRefresh()) }, - async loadHomeData() { - this.setData({ loading: true, loadError: false }) - try { - if (!authStore.state.isLoggedIn && this.isDevelopPreview()) { - this.processHomeData(previewData(), true) - return - } - if (!authStore.state.isLoggedIn) { - this.processHomeData({ ...previewData(), recommendations: [] }, false) - return - } - this.processHomeData(await api.get('/home'), false) - } catch (error) { - console.error('首页数据加载失败', error) - if (this.isDevelopPreview()) this.processHomeData(previewData(), true) - else this.setData({ loading: false, loadError: true, recState: 'error' }) - } - }, - isDevelopPreview() { - try { return wx.getAccountInfoSync().miniProgram.envVersion === 'develop' } catch (_) { return false } - }, - processHomeData(data, previewMode) { - const state = data.userState || null - const profileStatus = state && state.profileStatus - const eligibility = (state && state.localEligibilityStatus) || 'not_declared' - const relationship = (state && state.relationshipStatus) || '' - const isOnboarding = !state || (profileStatus !== 'approved' && profileStatus !== 'completed') - this.setData({ - loading: false, loadError: false, previewMode, - userState: state, isOnboarding, isVerified: eligibility === 'verified', - isPaused: relationship === 'paused', isSuccessful: relationship === 'matched_successfully', - onboardingMessage: isOnboarding ? '完成资料与资格核验后,即可开始寻找合适的人' : '', - onboardingActions: isOnboarding ? [{ text: '继续完善', event: authStore.state.isLoggedIn ? 'profile' : 'login', variant: 'primary' }] : [], - statusChecks: [{ label: '已实名', done: !!(state && state.verified) }, { label: '本地资格', done: eligibility === 'verified' }, { label: '资料已审核', done: profileStatus === 'approved' || profileStatus === 'completed' }], - banners: data.banners || [], quickEntries: (data.quickEntries && data.quickEntries.length ? data.quickEntries : QUICK_ENTRIES), - recommendations: data.recommendations || [], recState: data.recommendations && data.recommendations.length ? 'loaded' : 'empty', - serviceRecommendations: data.serviceRecommendations || [], activities: data.activities || [], serviceLocation: data.serviceLocation || null, - articles: data.articles || [], safetyNotice: data.safetyNotice || this.data.safetyNotice, - }) - }, - onRetry() { this.loadHomeData() }, - onBannerTap(e) { - const item = e.currentTarget.dataset.item || {} - if (item.targetType === 'activity' && item.targetValue) return wx.navigateTo({ url: `/pages/activity-detail/activity-detail?id=${item.targetValue}` }) - const safeRoutes = { matchmaker: '/pages/matchmaker/matchmaker', counseling: '/pages/counseling-intro/counseling-intro', premarital: '/pages/premarital-intro/premarital-intro', service_location: '/pages/service-station/service-station', article: '/pages/content-detail/content-detail', internal_page: item.targetValue } - const url = safeRoutes[item.targetType] - if (url && /^\/pages\//.test(url)) wx.navigateTo({ url }) - }, - onEntryTap(e) { - const entry = e.currentTarget.dataset.entry || {} - if (!entry.enabled) return wx.showToast({ title: '完成资料后可使用此服务', icon: 'none' }) - if (entry.tab) wx.switchTab({ url: entry.targetPage }) - else wx.navigateTo({ url: entry.targetPage }) - }, - onViewDetail(e) { if (e.detail.userId) wx.navigateTo({ url: `/pages/profile-detail/profile-detail?id=${e.detail.userId}` }) }, - onLike(e) { this.setData({ actionLoadingUserId: e.detail.userId }); setTimeout(() => { this.setData({ actionLoadingUserId: '' }); appStore.toastSuccess('已表达喜欢') }, 450) }, - onPass(e) { this.setData({ actionLoadingUserId: e.detail.userId }); setTimeout(() => this.setData({ actionLoadingUserId: '' }), 300) }, - onEmptyAction(e) { - const event = (e.detail && e.detail.event) || e.currentTarget.dataset.event - const routes = { login: '/pages/login/login', profile: '/pages/profile-basic/profile-basic', preference: '/pages/profile-preference/profile-preference', matchmaker: '/pages/matchmaker/matchmaker' } - if (event === 'discover') return wx.switchTab({ url: '/pages/discover/discover' }) - if (routes[event]) wx.navigateTo({ url: routes[event] }) - }, - onResume() { wx.navigateTo({ url: '/pages/relationship-status/relationship-status' }) }, - onSeeMore() { wx.switchTab({ url: '/pages/discover/discover' }) }, - onSeeAllActivities() { wx.switchTab({ url: '/pages/activity/activity' }) }, - onActivityTap(e) { wx.navigateTo({ url: `/pages/activity-detail/activity-detail?id=${e.currentTarget.dataset.id}` }) }, - onStationTap() { wx.navigateTo({ url: '/pages/service-station/service-station' }) }, - onServiceTap(e) { const routes={matchmaker:'/pages/matchmaker/matchmaker',counseling:'/pages/counseling-intro/counseling-intro',premarital:'/pages/premarital-intro/premarital-intro'}; if(routes[e.currentTarget.dataset.type]) wx.navigateTo({url:routes[e.currentTarget.dataset.type]}) }, - onArticleTap(e) { wx.navigateTo({ url: `/pages/content-detail/content-detail?id=${e.currentTarget.dataset.id}` }) }, - onSafetyTap() { wx.navigateTo({ url: '/pages/anti-fraud/anti-fraud' }) }, - onMessages() { wx.switchTab({ url: '/pages/messages/messages' }) }, -}) \ No newline at end of file