/** * UserStore(规范 §131) * * 职责: * - 维护 /me/status 返回的 UserState 聚合(决定路由 / 权限 / Tab) * - 维护本人资料 Profile 与 PartnerPreference 缓存 * - 维护实名认证状态、审核状态 * - 关系状态切换(暂停 / 恢复 / 正在了解) * * 不变量(AGENTS.md): * - 第 8 条:敏感数据默认拒绝——Profile 中 birthDate 完整值仅服务端持有, * 本地缓存不存身份证号、手机号、完整生日。 * - 第 2 条:Required 不可自动放宽——PartnerPreference 中的 required 字段 * 前端展示为硬约束标识,不允许任何自动软化为 preferred。 */ const BaseStore = require('./baseStore'); const { userService, profileService, verificationService } = require('../services'); const { STORAGE_KEYS, PROFILE_COMPLETION, PROFILE_REVIEW, ACCOUNT_STATUS } = require('../constants'); class UserStore extends BaseStore { constructor() { super({ /** @type {import('../types').UserState|null} */ userState: null, /** @type {import('../types').Profile|null} */ profile: null, /** @type {import('../types').PartnerPreference|null} */ preferences: null, /** @type {import('../types').PrivacySettings|null} */ privacySettings: null, /** @type {'idle'|'pending'|'approved'|'rejected'|'not_submitted'} */ verificationStatus: 'not_submitted', /** @type {'idle'|'loading'|'error'} */ loadStatus: 'idle', lastError: null, }); } /** 冷启动从 storage 还原轻量状态,避免白屏 */ hydrate() { const statusSnap = BaseStore.hydrate(STORAGE_KEYS.USER_STATUS_SNAPSHOT); if (statusSnap) this.setState({ userState: statusSnap }); const profileSnap = BaseStore.hydrate(STORAGE_KEYS.CURRENT_USER_PROFILE); if (profileSnap) this.setState({ profile: profileSnap }); } /** 拉取并缓存 /me/status(决定路由 / Tab / 权限) */ async fetchStatus() { this.setState({ loadStatus: 'loading', lastError: null }); try { const userState = await userService.getStatus(); this.setState({ userState, loadStatus: 'idle' }); this.persist(userState, STORAGE_KEYS.USER_STATUS_SNAPSHOT); return userState; } catch (err) { this.setState({ loadStatus: 'error', lastError: err }); throw err; } } /** 拉取本人资料 */ async fetchProfile() { const profile = await profileService.getProfile(); this.setState({ profile }); this.persist(profile, STORAGE_KEYS.CURRENT_USER_PROFILE); return profile; } /** * 更新本人资料 * 注意:本方法不校验字数,校验由调用方(页面)按 BUSINESS_LIMITS 执行 */ async updateProfile(payload) { const profile = await profileService.updateProfile(payload); this.setState({ profile }); this.persist(profile, STORAGE_KEYS.CURRENT_USER_PROFILE); return profile; } /** 拉取择偶条件 */ async fetchPreferences() { const preferences = await profileService.getPreferences(); this.setState({ preferences }); return preferences; } /** * 更新择偶条件 * 调用方需校验 required 字段不可自动放宽(AGENTS 第 2 条),UI 必须明确告知用户 */ async updatePreferences(payload) { const preferences = await profileService.updatePreferences(payload); this.setState({ preferences }); return preferences; } /** 拉取隐私设置 */ async fetchPrivacySettings() { const privacySettings = await profileService.getPrivacySettings(); this.setState({ privacySettings }); return privacySettings; } /** 更新隐私设置 */ async updatePrivacySettings(payload) { const privacySettings = await profileService.updatePrivacySettings(payload); this.setState({ privacySettings }); return privacySettings; } /** 拉取实名认证状态 */ async fetchVerificationStatus() { const result = await verificationService.getStatus(); this.setState({ verificationStatus: result.status }); return result; } /** 提交实名认证 */ async submitVerification(payload) { const result = await verificationService.submit(payload); this.setState({ verificationStatus: 'pending' }); return result; } /** 拉取审核状态 */ async fetchReviewStatus() { return profileService.getReviewStatus(); } /** * 提交资料审核 * * AGENTS 第 9 条:PostgreSQL 是 Source of Truth——审核状态以服务端返回为准, * 本地不预判结果;提交成功后同步更新 userState.profileReviewStatus 为 'pending', * 以便路由守卫立即把用户引导至"审核中"页面,避免重复提交。 * * @returns {Promise<{ reviewStatus: 'pending', submittedAt: number }>} */ async submitReview() { const result = await profileService.submitReview(); if (this.state.userState) { this.setState({ userState: { ...this.state.userState, profileReviewStatus: PROFILE_REVIEW.PENDING, profileCompletionStatus: PROFILE_COMPLETION.COMPLETED, }, }); this.persist(this.state.userState, STORAGE_KEYS.USER_STATUS_SNAPSHOT); } return result; } /** * 拉取婚恋关系状态(REST §83 章) * 同步更新 userState.relationshipStatus / recommendationEnabled / matchingWith * @returns {Promise<{ status: string, recommendationEnabled: boolean, matchingWith?: object }>} */ async fetchRelationshipStatus() { const result = await profileService.getRelationshipStatus(); if (this.state.userState) { this.setState({ userState: { ...this.state.userState, relationshipStatus: result.status, recommendationEnabled: result.recommendationEnabled, matchingWith: result.matchingWith || null, }, }); this.persist(this.state.userState, STORAGE_KEYS.USER_STATUS_SNAPSHOT); } return result; } /** 暂停寻找 */ async pauseSearching() { const result = await profileService.pauseSearching(); if (this.state.userState) { this.setState({ userState: { ...this.state.userState, relationshipStatus: 'paused', recommendationEnabled: result.recommendationEnabled ?? false, }, }); } return result; } /** 恢复寻找 */ async resumeSearching() { const result = await profileService.resumeSearching(); if (this.state.userState) { this.setState({ userState: { ...this.state.userState, relationshipStatus: 'searching', recommendationEnabled: result.recommendationEnabled ?? true, }, }); } return result; } /** 设置为正在了解 */ async markGettingToKnow() { const result = await profileService.markGettingToKnow(); if (this.state.userState) { this.setState({ userState: { ...this.state.userState, relationshipStatus: 'getting_to_know', recommendationEnabled: result.recommendationEnabled ?? false, }, }); } return result; } // ============ 派生 getters ============ get isLoggedIn() { return !!this.state.userState; } get isRealNameVerified() { return !!this.state.userState?.isRealNameVerified; } get isProfileCompleted() { return this.state.userState?.profileCompletionStatus === PROFILE_COMPLETION.COMPLETED; } get isProfileApproved() { return this.state.userState?.profileReviewStatus === PROFILE_REVIEW.APPROVED; } get isAccountNormal() { return this.state.userState?.accountStatus === ACCOUNT_STATUS.NORMAL; } /** 推荐页是否可见(综合账户态 + 隐私设置) */ get recommendationEnabled() { return !!this.state.userState?.recommendationEnabled && this.isAccountNormal; } } const userStore = new UserStore(); module.exports = userStore;