// utils/base-page.js - 页面公共框架 import request from './request.js'; import popupService from '../services/popupService.js'; import { isRoleStatusActive, isCenterPageActive, syncRoleStatuses, syncProfileFields, syncGroupFields, ensureRoleOnCenterPage, clearCacheAndEnterNormal, refreshTabBar, } from './role-tab-bar.js'; import { lockPrimaryRole, migrateLegacyCenterRole, enterLockedRole } from './primary-role.js'; import { isMerchantPortalUser } from './staff-api.js'; import { resolveAvatarFromStorage } from './avatar.js'; import { isApiSuccess, getApiMsg, extractUserData, parseSceneOptions } from './api-helper.js'; // ===== 基类数据 ===== const BASE_DATA = { isLoading: false, lastRefreshTime: 0, canRefresh: true, }; // ===== 基类方法 ===== const BASE_METHODS = { // ---------- 通知组件 ---------- registerNotificationComponent() { const app = getApp(); const comp = this.selectComponent('#global-notification'); if (comp && comp.showNotification) { app.globalData.globalNotification = { show: (data) => comp.showNotification(data), hide: () => comp.hideNotification(), }; } }, // ---------- 弹窗清理 ---------- cleanupPopup() { const comp = this.selectComponent('#popupNotice'); if (comp && comp.cleanup) comp.cleanup(); }, // ---------- 冷启动弹窗 ---------- checkColdStartPopup(pageId) { const app = getApp(); if (!app.globalData.hasShownPopupOnColdStart) { app.globalData.hasShownPopupOnColdStart = true; setTimeout(() => popupService.checkAndShow(this, pageId), 300); } }, // ---------- 头像加载 ---------- loadAvatar() { this.setData({ avatarUrl: resolveAvatarFromStorage(getApp()) }); }, // ---------- 通用用户信息加载 ---------- loadUserInfo() { const app = getApp(); const { nick, uid, avatarUrl } = (app.getUserProfileForDisplay && app.getUserProfileForDisplay()) || {}; this.setData({ avatarUrl: avatarUrl || resolveAvatarFromStorage(app), uid: uid || wx.getStorageSync('uid') || '', nicheng: nick || wx.getStorageSync('nicheng') || '', }); }, // ---------- 通用数据加载 ---------- async loadData(opts) { const { url, method = 'POST', data: reqData, fields, loadingKey = 'isLoading', successCode = 200, errorMsg = '加载失败', onSuccess, onError, } = opts; this.setData({ [loadingKey]: true }); try { const res = await request({ url, method, data: reqData }); if (res && (res.data.code === successCode || (successCode === 0 && res.data.code === 0))) { const resData = res.data.data || {}; if (fields) { const updateData = {}; Object.keys(fields).forEach((pageKey) => { const apiField = fields[pageKey]; updateData[pageKey] = apiField in resData ? resData[apiField] : (typeof apiField === 'object' ? apiField.default : ''); }); this.setData({ ...updateData, [loadingKey]: false }); } if (onSuccess) onSuccess(resData, res); return resData; } throw new Error(res?.data?.msg || res?.data?.message || errorMsg); } catch (error) { this.setData({ [loadingKey]: false }); if (onError) { onError(error); } else { wx.showToast({ title: error.message || errorMsg, icon: 'none' }); } return null; } }, // ---------- 节流刷新 ---------- throttledRefresh(fn, interval = 3000) { const now = Date.now(); if (!this.data.canRefresh || (now - this.data.lastRefreshTime < interval)) { wx.showToast({ title: '操作过快', icon: 'none' }); return; } this.setData({ lastRefreshTime: now, canRefresh: false }); fn.call(this); setTimeout(() => this.setData({ canRefresh: true }), interval); }, // ---------- 复制 UID ---------- copyUid() { const uid = this.data.uid; if (!uid) { wx.showToast({ title: 'UID不存在', icon: 'none' }); return; } wx.setClipboardData({ data: uid, success: () => wx.showToast({ title: '已复制UID', icon: 'success' }), fail: () => wx.showToast({ title: '复制失败', icon: 'none' }), }); }, // ---------- 清除缓存 ---------- clearCache() { wx.showModal({ title: '清除缓存', content: '将清除所有登录与身份数据,并回到点单端,确定继续?', confirmText: '确定', cancelText: '取消', success: (r) => { if (r.confirm) clearCacheAndEnterNormal(this); }, }); }, // ---------- 返回点单端 ---------- switchToNormal() { lockPrimaryRole('normal'); wx.reLaunch({ url: '/pages/mine/mine' }); }, // ---------- 客服 ---------- goToKefu() { const app = getApp(); const { link, enterpriseId } = app.globalData.kefuConfig || {}; if (!link || !enterpriseId) { wx.showToast({ title: '客服配置未加载', icon: 'none' }); return; } wx.openCustomerServiceChat({ extInfo: { url: link }, corpId: enterpriseId }); }, // ---------- API 辅助(快捷访问) ---------- _isApiSuccess: isApiSuccess, _getApiMsg: getApiMsg, _extractUserData: extractUserData, // ---------- 邀请码注册通用流程 ---------- async registerWithInviteCode(opts) { const { inviteCode, apiUrl, role, statusKey, alreadyMsg, successMsg = '注册成功', onSuccess, } = opts; if (!inviteCode) { wx.showToast({ title: '请输入邀请码', icon: 'none' }); return; } if (inviteCode.length > 100) { wx.showToast({ title: '邀请码不能超过100字符', icon: 'none' }); return; } this.setData({ isLoading: true }); try { const res = await request({ url: apiUrl, method: 'POST', data: { inviteCode } }); const body = res?.data || {}; if (isApiSuccess(body)) { const userData = extractUserData(body); syncRoleStatuses(userData); syncProfileFields(userData); syncGroupFields(userData); if (userData.token) { wx.setStorageSync('token', userData.token); getApp().globalData.token = userData.token; } if (!Object.prototype.hasOwnProperty.call(userData, statusKey) && !isRoleStatusActive(wx.getStorageSync(statusKey))) { wx.setStorageSync(statusKey, 1); getApp().globalData[statusKey] = 1; } if (onSuccess) { await onSuccess(userData, body); } this.setData({ isLoading: false }); wx.showToast({ title: successMsg, icon: 'success' }); } else { const msg = getApiMsg(body); if (alreadyMsg && msg && msg.includes(alreadyMsg)) { if (onSuccess) { await onSuccess(extractUserData(body), body); } this.setData({ isLoading: false }); wx.showToast({ title: msg, icon: 'success' }); } else { this.setData({ isLoading: false }); wx.showToast({ title: msg || '注册失败', icon: 'none' }); } } } catch (error) { this.setData({ isLoading: false }); wx.showToast({ title: error.message || '注册失败', icon: 'none' }); } }, // ---------- 邀请码 + 登录检查 ---------- handleInviteCodeWithLoginCheck(opts) { const { inviteCode, role, statusKey, alreadyMsg, onNeedLogin, onAlreadyRegistered } = opts; const token = wx.getStorageSync('token'); const uid = wx.getStorageSync('uid'); if (!token || !uid) { if (onNeedLogin) onNeedLogin(inviteCode); return; } const isActive = isRoleStatusActive(wx.getStorageSync(statusKey)); if (!isActive) { wx.showLoading({ title: '注册中...', mask: true }); this.registerWithInviteCode(opts); } else { if (onAlreadyRegistered) onAlreadyRegistered(); else wx.showToast({ title: alreadyMsg || '您已注册', icon: 'success' }); } }, // ---------- 微信登录 + 注册 ---------- loginAndRegisterWithInviteCode(opts) { const { inviteCode, onLoginSuccess } = opts; wx.showLoading({ title: '登录注册中...', mask: true }); wx.login({ success: (loginRes) => { if (loginRes.code) { if (onLoginSuccess) onLoginSuccess(loginRes.code, inviteCode); } else { wx.hideLoading(); wx.showToast({ title: '微信登录失败', icon: 'none' }); } }, fail: () => { wx.hideLoading(); wx.showToast({ title: '微信登录失败', icon: 'none' }); }, }); }, // ---------- 图片 URL 初始化 ---------- initImageUrls(urlMap) { const ossImageUrl = getApp().globalData.ossImageUrl || ''; const result = {}; Object.keys(urlMap).forEach((key) => { const path = urlMap[key]; result[key] = path.startsWith('http') || path.startsWith('/') ? path : ossImageUrl + path; }); return result; }, }; // ===== 角色中心页增强 ===== function createRoleCenterMixin(roleConfig) { const { role, statusKey, dataFlag, pageId, apiUrl } = roleConfig; return { onReady() { if (isCenterPageActive(this, dataFlag, statusKey)) { ensureRoleOnCenterPage(this, role); } }, onHide() { this.cleanupPopup(); }, checkRoleStatus() { try { const status = wx.getStorageSync(statusKey); const uid = wx.getStorageSync('uid'); const active = role === 'shangjia' ? isMerchantPortalUser() : isRoleStatusActive(status); if (active) { this.setData({ [dataFlag]: true, uid: uid || '' }); this.loadAvatar(); if (this.checkGlobalData) this.checkGlobalData(); ensureRoleOnCenterPage(this, role); } else { this.setData({ [dataFlag]: false }); } } catch (error) { this.setData({ [dataFlag]: false }); } }, }; } /** * 创建页面配置(合并基类方法) * @param {Object} pageConfig - 页面特有配置 * @param {Object} [options] - 可选配置 * @param {Object} [options.roleConfig] - 角色中心页配置 { role, statusKey, dataFlag, pageId, apiUrl } * @returns {Object} 完整的 Page 配置 */ export function createPage(pageConfig, options = {}) { const { roleConfig } = options; // 合并 data const data = { ...BASE_DATA, ...(pageConfig.data || {}) }; // 合并方法 const methods = { ...BASE_METHODS }; // 如果是角色中心页,混入角色相关方法 if (roleConfig) { Object.assign(methods, createRoleCenterMixin(roleConfig)); } // 页面特有方法覆盖基类方法 const allMethods = { ...methods, ...pageConfig }; // 确保 data 正确 allMethods.data = data; return allMethods; } // 导出工具函数供页面直接使用 export { request, isRoleStatusActive, isCenterPageActive, syncRoleStatuses, syncProfileFields, syncGroupFields, ensureRoleOnCenterPage, clearCacheAndEnterNormal, refreshTabBar, lockPrimaryRole, migrateLegacyCenterRole, enterLockedRole, isApiSuccess, getApiMsg, extractUserData, parseSceneOptions, };