Files

239 lines
8.0 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.
/**
* stores 聚合入口(规范 §131)
*
* 暴露 7 个 Store 单例 + 跨 Store 编排辅助函数 + 启动初始化 bootstrap。
*
* 跨 Store 编排原则:
* - 单个 Store 自身只维护自己的状态,不直接 require 其他 Store(避免循环依赖)。
* - 涉及多 Store 副作用的动作(如 Block 后会话停用、Mutual 后会话刷新)通过本文件
* 暴露的辅助函数编排,调用方(页面)使用这些辅助函数而非直接组合多个 Store。
*
* 用法(页面):
* const { matchStore, appStore, blockUser } = require('../../stores');
* await blockUser(userId, '骚扰');
*/
const authStore = require('./authStore');
const userStore = require('./userStore');
const matchStore = require('./matchStore');
const messageStore = require('./messageStore');
const activityStore = require('./activityStore');
const matchmakerStore = require('./matchmakerStore');
const appStore = require('./appStore');
const { ROUTE, PROFILE_REVIEW, ACCOUNT_STATUS, buildPath } = require('../constants');
// ============ Review 状态路由守卫(P05-T12,规范 §4.3) ============
//
// AGENTS 第 9 条:PostgreSQL 是 Source of Truth——守卫只读 userStore.state.userState,
// 不预判、不缓存;状态变化时由各页 onShow 调用 requireApproved 自检并跳转。
//
// 受保护路由分级:
// - 'approved':资料审核通过后方可访问(推荐/找对象/缘分中心/用户详情/活动报名)
// - 'searching':关系态为 searching 方可访问(部分推荐入口)
// - 'logged_in':仅需登录(消息/我的/设置等)
// 审核未通过时一律引导至 REVIEW.PENDING 或 REVIEW.REJECTED。
/**
* 受保护路由 → 所需最低审核态映射
* 未列出的路由默认为 'logged_in'(仅需登录)
*/
const ROUTE_ACCESS_REQUIREMENTS = {
// 推荐 / 找对象(PROFILE_NOT_APPROVED 错误码兜底,但客户端预拦截体验更好)
[ROUTE.TAB.DISCOVER]: 'approved',
[ROUTE.MAIN.DISCOVER]: 'approved',
[ROUTE.MAIN.FATE_CENTER]: 'approved',
[ROUTE.DETAIL.PROFILE_DETAIL]: 'approved',
[ROUTE.DETAIL.ACTIVITY_REGISTER]: 'approved',
// 红娘申请需审核通过
[ROUTE.DETAIL.MATCHMAKER_APPLY]: 'approved',
};
/**
* 读取当前用户的审核态(来自 userState.profileReviewStatus,仅本地读取)
* @returns {string} PROFILE_REVIEW 值;未登录或无状态时返回 NOT_SUBMITTED
*/
function readReviewStatus() {
return userStore.state.userState?.profileReviewStatus || PROFILE_REVIEW.NOT_SUBMITTED;
}
/**
* 根据当前审核态决定应跳转的"审核态页面"
* @returns {string|null} 需要跳转的路径;approved 返回 null
*/
function getReviewRedirectPath() {
const status = readReviewStatus();
if (status === PROFILE_REVIEW.PENDING) return ROUTE.REVIEW.PENDING;
if (status === PROFILE_REVIEW.NEED_EDIT || status === PROFILE_REVIEW.REJECTED) {
return ROUTE.REVIEW.REJECTED;
}
return null;
}
/**
* 校验当前用户是否可访问指定路径
* @param {string} path 路由常量
* @returns {{ok: boolean, redirect?: string}} ok=true 可访问;ok=false 时 redirect 为应跳转路径
*/
function canAccess(path) {
const required = ROUTE_ACCESS_REQUIREMENTS[path];
if (!required || required === 'logged_in') return { ok: true };
const reviewStatus = readReviewStatus();
const accountStatus = userStore.state.userState?.accountStatus || ACCOUNT_STATUS.NORMAL;
// 账号异常态优先拦截
if (accountStatus !== ACCOUNT_STATUS.NORMAL) {
return { ok: false, redirect: ROUTE.REVIEW.ACCOUNT_STATUS };
}
if (required === 'approved' && reviewStatus !== PROFILE_REVIEW.APPROVED) {
const redirect = getReviewRedirectPath();
return { ok: false, redirect: redirect || ROUTE.REVIEW.PENDING };
}
return { ok: true };
}
/**
* 守卫式导航:检查审核态后再决定跳转目标
*
* 用法:
* const { guardedNavigate } = require('../../stores');
* guardedNavigate(ROUTE.MAIN.DISCOVER); // 未审核则自动跳 review-pending
*
* @param {string} path 目标路由常量
* @param {Record<string, string|number>} [params] 仅 id 类参数
* @returns {boolean} 是否成功发起导航(true=已跳转到目标或审核页)
*/
function guardedNavigate(path, params) {
const access = canAccess(path);
if (access.ok) {
const target = params ? buildPath(path, params) : path;
wx.navigateTo({ url: target });
return true;
}
if (access.redirect) {
wx.redirectTo({ url: access.redirect });
}
return false;
}
/**
* 当前用户是否已通过资料审核
* @returns {boolean}
*/
function isProfileApproved() {
return readReviewStatus() === PROFILE_REVIEW.APPROVED;
}
// ============ 跨 Store 编排 ============
/**
* 拉黑用户:触发 MatchStore 与 MessageStore 同步副作用
* - MatchStore:从推荐/Mutual 列表移除该用户
* - MessageStore:标记对应会话不可发消息(AGENTS 第 4 条)
* @param {string} userId
* @param {string} [reason]
*/
async function blockUser(userId, reason) {
await matchStore.block(userId, reason);
// 查找对应会话并标记停用
const conv = messageStore.state.conversations.find(
(c) => c.targetUserId === userId || c.conversationId === userId,
);
if (conv) messageStore.markConversationBlocked(conv.conversationId);
}
/**
* Like 用户:若返回 mutualMatch=true,自动刷新会话列表
* @param {string} userId
* @returns {Promise<import('../types').LikeResult>}
*/
async function likeUser(userId) {
const result = await matchStore.like(userId);
if (result && result.mutualMatch) {
// Mutual Match 达成,刷新会话列表,让用户能立即开始聊天
await messageStore.fetchConversations();
}
return result;
}
/**
* 解除匹配:触发 MatchStore 与 MessageStore 同步副作用
* @param {string} matchId
*/
async function unmatchConversation(matchId) {
await matchStore.unmatch(matchId);
messageStore.markConversationBlocked(matchId);
}
/**
* 成功牵手:双确认第二步(仅被请求方调用),成功后刷新 userStore 与会话列表
* @param {string} matchId
*/
async function confirmSuccess(matchId) {
const result = await matchStore.confirmSuccess(matchId);
// 成功牵手后关系状态变化,需刷新 userState
try { await userStore.fetchStatus(); } catch (e) { /* 不阻塞主流程 */ }
try { await messageStore.fetchConversations(); } catch (e) { /* 同上 */ }
return result;
}
// ============ 启动初始化 ============
/**
* App.onLaunch 调用,按依赖顺序初始化所有 Store
* 1. appStore.init() — 拉取服务端配置(不阻塞核心)
* 2. authStore.hydrate() — 从本地恢复登录态
* 3. userStore.hydrate() — 从本地恢复用户状态快照
* 4. messageStore.hydrate() — 从本地恢复未读数(兜底)
* 5. 若已登录,异步刷新 userStore.fetchStatus()(不阻塞启动)
*/
async function bootstrap() {
// 同步阶段:本地状态恢复(避免白屏)
authStore.hydrate();
userStore.hydrate();
messageStore.hydrate();
// 异步阶段:服务端配置 + 用户状态刷新
// 配置失败不阻塞启动;状态失败由路由守卫处理(跳登录页)
appStore.init().catch(() => {});
if (authStore.state.isLoggedIn) {
userStore.fetchStatus().catch(() => {});
} else {
// 未登录:跳转到登录页(使用 navigateTo 而非 redirectTo,因为首页是 Tab 页)
setTimeout(() => {
try {
wx.navigateTo({ url: ROUTE.AUTH.LOGIN });
} catch (e) {
console.warn('[bootstrap] 跳转登录页失败:', e);
}
}, 300);
}
}
module.exports = {
// 单例 Store
authStore,
userStore,
matchStore,
messageStore,
activityStore,
matchmakerStore,
appStore,
// 跨 Store 编排辅助
blockUser,
likeUser,
unmatchConversation,
confirmSuccess,
// Review 状态路由守卫(P05-T12)
readReviewStatus,
getReviewRedirectPath,
canAccess,
guardedNavigate,
isProfileApproved,
ROUTE_ACCESS_REQUIREMENTS,
// 启动入口
bootstrap,
};