239 lines
8.0 KiB
JavaScript
239 lines
8.0 KiB
JavaScript
/**
|
||||
|
|
* 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,
|
|||
|
|
};
|