Files

151 lines
5.1 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.
/**
* 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;