Files

249 lines
7.8 KiB
JavaScript
Raw Permalink Normal View History

2026-09-09 22:05:00 +08:00
/**
* 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;