Files
danyang-match/miniapp/stores/appStore.js
T

151 lines
5.1 KiB
JavaScript
Raw Normal View History

2026-09-09 22:05:00 +08:00
/**
* AppStore(规范 §131)
*
* 职责:
* - 维护运行时业务配置(来自服务端,覆盖 BUSINESS_LIMITS 默认值)
* - 维护平台维护态(503 时由 apiClient 注入)
* - 维护网络状态(断网/弱网提示)
* - 提供 toast / loading / modal 统一封装,避免页面散落 wx.* UI 调用
*
* 不变量(AGENTS.md 第 9 条):PostgreSQL 是 Source of Truth——
* 本 Store 持有的 businessConfig 仅是服务端配置的运行时缓存,
* 任何前端缓存的配置都不能覆盖后端校验。
*/
const BaseStore = require('./baseStore');
const api = require('../utils/api');
const { appConfigService } = require('../services');
const { BUSINESS_LIMITS, mapErrorMessage } = require('../constants');
class AppStore extends BaseStore {
constructor() {
super({
/** @type {typeof BUSINESS_LIMITS} 运行时合并后的业务配置 */
businessConfig: { ...BUSINESS_LIMITS },
/** @type {boolean} 平台维护态 */
maintenance: false,
maintenanceMessage: '',
/** @type {boolean} 网络在线 */
networkOnline: true,
/** @type {boolean} 全局 loading 显示中 */
loadingVisible: false,
loadingText: '',
/** @type {boolean} 新手引导是否已关闭(按 storage 记忆) */
newbieTipsClosed: false,
});
// 注入维护态拦截回调(apiClient 503 时调用)
api.onMaintenance = (config) => this.handleMaintenance(config);
// 监听网络状态
if (typeof wx !== 'undefined' && wx.onNetworkStatusChange) {
wx.onNetworkStatusChange((res) => {
this.setState({ networkOnline: !!res.isConnected });
});
// 启动时同步一次当前网络状态
if (wx.getNetworkType) {
wx.getNetworkType({
success: (res) => {
this.setState({ networkOnline: res.networkType !== 'none' });
},
});
}
}
}
/** 冷启动初始化:拉取服务端配置覆盖默认值(规范 §118) */
async init() {
try {
const config = await appConfigService.fetchAppConfig();
this.setState({ businessConfig: config });
} catch (e) {
// 接口失败时使用默认值(规范 §119),不阻塞启动
this.setState({ businessConfig: { ...BUSINESS_LIMITS } });
}
}
/** 503 维护态进入:弹维护提示,后续请求会被 apiClient 自动拦截 */
handleMaintenance(config = {}) {
this.setState({
maintenance: true,
maintenanceMessage: config.message || '平台正在维护,请稍后再试',
});
if (typeof wx !== 'undefined' && wx.showToast) {
wx.showToast({
title: this.state.maintenanceMessage,
icon: 'none',
duration: 3000,
});
}
}
/** 清除维护态(一般由刷新页面后重新拉取 /me/status 时恢复) */
clearMaintenance() {
this.setState({ maintenance: false, maintenanceMessage: '' });
}
/**
* 统一 Toast(规范 §56:1.5~2 秒)
* @param {string} message
* @param {'success'|'error'|'warning'|'none'} [type='none']
*/
toast(message, type = 'none') {
if (typeof wx === 'undefined' || !wx.showToast) return;
const iconMap = { success: 'success', error: 'error', warning: 'none', none: 'none' };
wx.showToast({
title: message || '',
icon: iconMap[type] || 'none',
duration: this.state.businessConfig.TOAST_DURATION || BUSINESS_LIMITS.TOAST_DURATION,
});
}
/** 成功提示 */
toastSuccess(message) { this.toast(message, 'success'); }
/** 错误提示(自动从 err 中提取用户可理解文案,规范 §80-84) */
toastError(err, fallback = '操作失败,请稍后再试') {
const code = err && err.code;
const message = code ? mapErrorMessage(code, fallback) : (err && err.message) || fallback;
this.toast(message, 'none');
}
/** 全局 loading */
showLoading(text = '加载中...') {
if (this.state.loadingVisible) return;
this.setState({ loadingVisible: true, loadingText: text });
if (typeof wx !== 'undefined' && wx.showLoading) {
wx.showLoading({ title: text, mask: true });
}
}
hideLoading() {
if (!this.state.loadingVisible) return;
this.setState({ loadingVisible: false, loadingText: '' });
if (typeof wx !== 'undefined' && wx.hideLoading) wx.hideLoading();
}
/**
* 统一确认对话框(用于危险操作:拉黑/解除匹配/暂停寻找/成功牵手确认)
* @param {{ title?: string, content: string, confirmText?: string, cancelText?: string, danger?: boolean }} opts
* @returns {Promise<boolean>} 用户是否确认
*/
confirm(opts) {
return new Promise((resolve) => {
if (typeof wx === 'undefined' || !wx.showModal) return resolve(false);
wx.showModal({
title: opts.title || (opts.danger ? '请确认' : '提示'),
content: opts.content,
confirmText: opts.confirmText || '确认',
cancelText: opts.cancelText || '取消',
confirmColor: opts.danger ? '#E04C4C' : '#1A6B3E',
success: (res) => resolve(!!res.confirm),
fail: () => resolve(false),
});
});
}
}
const appStore = new AppStore();
module.exports = appStore;