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;
|