/** * 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} 用户是否确认 */ 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;