249 lines
7.8 KiB
JavaScript
249 lines
7.8 KiB
JavaScript
/**
|
||
* 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;
|