Files
xingque/utils/base-page.js

363 lines
11 KiB
JavaScript

// 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 { 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');
if (isRoleStatusActive(status)) {
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,
};