Files

249 lines
7.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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;