367 lines
11 KiB
JavaScript
367 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 { 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,
|
|
};
|