@@ -12,6 +12,7 @@ import {
|
||||
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';
|
||||
|
||||
@@ -296,7 +297,10 @@ function createRoleCenterMixin(roleConfig) {
|
||||
try {
|
||||
const status = wx.getStorageSync(statusKey);
|
||||
const uid = wx.getStorageSync('uid');
|
||||
if (isRoleStatusActive(status)) {
|
||||
const active = role === 'shangjia'
|
||||
? isMerchantPortalUser()
|
||||
: isRoleStatusActive(status);
|
||||
if (active) {
|
||||
this.setData({ [dataFlag]: true, uid: uid || '' });
|
||||
this.loadAvatar();
|
||||
if (this.checkGlobalData) this.checkGlobalData();
|
||||
|
||||
@@ -165,13 +165,15 @@ function connectGoEasy(app, userId, identityType) {
|
||||
loadConversations(app);
|
||||
},
|
||||
onFailed: (error) => {
|
||||
console.error('GoEasy连接失败', error);
|
||||
if (error.code === 408) {
|
||||
// 408 多为 GoEasy 节点超时,开发者工具常见,不影响订单等业务接口
|
||||
if (error && error.code === 408) {
|
||||
console.warn('GoEasy 连接超时(408),将使用本地会话缓存');
|
||||
bindGlobalListenersIfNeeded(app, userId);
|
||||
loadConversations(app);
|
||||
} else {
|
||||
wx.showToast({ title: '连接失败,请重试', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
console.warn('GoEasy连接失败', error);
|
||||
wx.showToast({ title: '消息服务连接失败', icon: 'none' });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
64
utils/club-context.js
Normal file
64
utils/club-context.js
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 多俱乐部上下文(读取 config/club-config.js 中的固定 club_id)
|
||||
*/
|
||||
import { CLUB_ID as CONFIG_CLUB_ID, WX_APP_ID } from '../config/club-config';
|
||||
|
||||
const CLUB_ID_KEY = 'club_id';
|
||||
const CLUB_SCOPE_KEY = 'club_scope';
|
||||
const DEFAULT_CLUB_ID = CONFIG_CLUB_ID || 'xq';
|
||||
|
||||
export const CLUB_API = {
|
||||
wechatLogin: '/jituan/auth/wechat-login',
|
||||
dashouRegister: '/jituan/auth/dashou-register',
|
||||
clubList: '/jituan/club/list',
|
||||
};
|
||||
|
||||
export function getConfiguredClubId() {
|
||||
return DEFAULT_CLUB_ID;
|
||||
}
|
||||
|
||||
export function getWxAppId() {
|
||||
return WX_APP_ID || '';
|
||||
}
|
||||
|
||||
export function getClubId(app) {
|
||||
const configured = getConfiguredClubId();
|
||||
const stored = wx.getStorageSync(CLUB_ID_KEY);
|
||||
if (stored && stored === configured) {
|
||||
return stored;
|
||||
}
|
||||
if (stored && stored !== configured) {
|
||||
wx.removeStorageSync(CLUB_ID_KEY);
|
||||
}
|
||||
app = app || getApp();
|
||||
if (app && app.globalData && app.globalData.clubId === configured) {
|
||||
return app.globalData.clubId;
|
||||
}
|
||||
return configured;
|
||||
}
|
||||
|
||||
export function setClubId(clubId, app) {
|
||||
const configured = getConfiguredClubId();
|
||||
const id = (clubId || configured).trim() || configured;
|
||||
if (id !== configured) {
|
||||
console.warn('[club] 登录返回 club_id 与工程配置不一致,以工程配置为准', id, configured);
|
||||
}
|
||||
const finalId = configured;
|
||||
app = app || getApp();
|
||||
wx.setStorageSync(CLUB_ID_KEY, finalId);
|
||||
if (app && app.globalData) {
|
||||
app.globalData.clubId = finalId;
|
||||
}
|
||||
return finalId;
|
||||
}
|
||||
|
||||
export function getClubScope() {
|
||||
return wx.getStorageSync(CLUB_SCOPE_KEY) || 'single';
|
||||
}
|
||||
|
||||
export function buildClubHeaders(extra = {}) {
|
||||
return {
|
||||
...extra,
|
||||
'X-Club-Id': getClubId(),
|
||||
};
|
||||
}
|
||||
13296
utils/cos-wx-sdk-v5.js
13296
utils/cos-wx-sdk-v5.js
File diff suppressed because one or more lines are too long
1
utils/cos-wx-sdk-v5.min.js
vendored
Normal file
1
utils/cos-wx-sdk-v5.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
31
utils/dashou-profile.js
Normal file
31
utils/dashou-profile.js
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 从服务端刷新打手会员/押金/积分等到 globalData(抢单、考试等页共用)
|
||||
*/
|
||||
import request from './request.js';
|
||||
|
||||
export async function refreshDashouMembership(app) {
|
||||
app = app || getApp();
|
||||
try {
|
||||
const res = await request({ url: '/yonghu/dashouxinxi', method: 'POST' });
|
||||
if (res && res.data && res.data.code == 200) {
|
||||
const data = res.data.data || {};
|
||||
const clumber = Array.isArray(data.clumber) ? data.clumber : [];
|
||||
app.globalData.clumber = clumber;
|
||||
if (data.yajin !== undefined) app.globalData.yajin = data.yajin;
|
||||
if (data.jifen !== undefined) app.globalData.jinfen = data.jifen;
|
||||
if (data.zhanghaostatus !== undefined) app.globalData.zhanghaoStatus = data.zhanghaostatus;
|
||||
if (data.dashouzhuangtai !== undefined) app.globalData.dashouzhuangtai = data.dashouzhuangtai;
|
||||
return clumber;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('refreshDashouMembership failed', e);
|
||||
}
|
||||
return app.globalData.clumber || [];
|
||||
}
|
||||
|
||||
/** 用户是否持有某会员类型(与抢单卡片判断一致) */
|
||||
export function userHasHuiyuan(huiyuanList, huiyuanId) {
|
||||
if (!huiyuanId) return true;
|
||||
const list = huiyuanList || [];
|
||||
return list.some((h) => String(h.huiyuanid) === String(huiyuanId));
|
||||
}
|
||||
42
utils/display-config.js
Normal file
42
utils/display-config.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 按俱乐部拉取公告/轮播(必须带 X-Club-Id,否则会落到默认 xq)
|
||||
*/
|
||||
import { buildClubHeaders, getClubId } from './club-context';
|
||||
|
||||
export function isGonggaoCacheValid(app) {
|
||||
const g = app.globalData || {};
|
||||
const cid = getClubId(app);
|
||||
return g._gonggaoClubId === cid && (g.shangpingonggao || (g.shangpinlunbo && g.shangpinlunbo.length));
|
||||
}
|
||||
|
||||
export function applyGonggaoToGlobal(app, data) {
|
||||
if (!data) return;
|
||||
const g = app.globalData;
|
||||
g.shangpingonggao = data.shangpingonggao || '';
|
||||
g.shangpinlunbo = data.shangpinlunbo || [];
|
||||
g._gonggaoClubId = getClubId(app);
|
||||
}
|
||||
|
||||
export function fetchGonggaoLunbo(app, pageKey = 'order_pool') {
|
||||
const apiBase = app.globalData && app.globalData.apiBaseUrl;
|
||||
if (!apiBase) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.request({
|
||||
url: `${apiBase}/peizhi/shangpingonggao/`,
|
||||
method: 'POST',
|
||||
header: buildClubHeaders({ 'content-type': 'application/json' }),
|
||||
data: { page_key: pageKey },
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data) {
|
||||
applyGonggaoToGlobal(app, res.data);
|
||||
resolve(res.data);
|
||||
} else {
|
||||
reject(new Error('shangpingonggao 请求失败'));
|
||||
}
|
||||
},
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
getSessionToken,
|
||||
isWechatLoginSuccess,
|
||||
} from './role-tab-bar';
|
||||
import { restoreStaffContextAfterAuth } from './staff-api.js';
|
||||
import { CLUB_API, getClubId, setClubId, buildClubHeaders } from './club-context';
|
||||
|
||||
const app = getApp();
|
||||
|
||||
@@ -14,15 +16,14 @@ export function ensureLogin(options = {}) {
|
||||
const token = getSessionToken(app);
|
||||
if (token) {
|
||||
backfillUserProfileCache(app);
|
||||
restoreStaffContextAfterAuth();
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
// 关键:如果已有登录流程正在执行,直接返回那个 Promise,避免并发登录
|
||||
if (app.globalData.loginPromise) {
|
||||
return app.globalData.loginPromise;
|
||||
}
|
||||
|
||||
// 开始新的登录流程
|
||||
const loginPromise = new Promise(async (resolve, reject) => {
|
||||
if (silent) {
|
||||
wx.showLoading({ title: '自动登录中...', mask: true });
|
||||
@@ -34,12 +35,15 @@ export function ensureLogin(options = {}) {
|
||||
const apiBaseUrl = app.globalData.apiBaseUrl;
|
||||
if (!apiBaseUrl) throw new Error('服务器配置错误');
|
||||
|
||||
const clubId = getClubId(app);
|
||||
const appId = app.globalData.appId || '';
|
||||
|
||||
const res = await new Promise((resolve, reject) => {
|
||||
wx.request({
|
||||
url: apiBaseUrl + '/yonghu/wechatlogin',
|
||||
url: apiBaseUrl + CLUB_API.wechatLogin,
|
||||
method: 'POST',
|
||||
data: { code: loginRes.code },
|
||||
header: { 'content-type': 'application/json' },
|
||||
data: { code: loginRes.code, club_id: clubId, app_id: appId },
|
||||
header: buildClubHeaders({ 'content-type': 'application/json' }),
|
||||
success: resolve,
|
||||
fail: reject,
|
||||
});
|
||||
@@ -47,7 +51,11 @@ export function ensureLogin(options = {}) {
|
||||
|
||||
const userData = res.data && (res.data.data || res.data);
|
||||
if (res.statusCode === 200 && isWechatLoginSuccess(res.data) && userData) {
|
||||
if (userData.club_id) {
|
||||
setClubId(userData.club_id, app);
|
||||
}
|
||||
applyWechatLoginData(userData, page);
|
||||
restoreStaffContextAfterAuth();
|
||||
resolve(true);
|
||||
} else {
|
||||
const msg = res.data?.msg || '登录失败';
|
||||
@@ -62,12 +70,10 @@ export function ensureLogin(options = {}) {
|
||||
}
|
||||
});
|
||||
|
||||
// 存储全局登录 Promise,供 request.js 感知
|
||||
app.globalData.loginPromise = loginPromise;
|
||||
// 无论成功或失败,完成后清空标记
|
||||
loginPromise.finally(() => {
|
||||
app.globalData.loginPromise = null;
|
||||
});
|
||||
|
||||
return loginPromise;
|
||||
}
|
||||
}
|
||||
|
||||
95
utils/merchant-order-stats.js
Normal file
95
utils/merchant-order-stats.js
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* 商家订单统计:优先新接口,失败时回退订单列表中的 pending 字段
|
||||
*/
|
||||
import { STAFF_API, isStaffMode } from './staff-api.js';
|
||||
|
||||
const EMPTY_ORDER = {
|
||||
pending_count: 0,
|
||||
pending_amount: '0.00',
|
||||
completed_count: 0,
|
||||
completed_amount: '0.00',
|
||||
refund_count: 0,
|
||||
refund_amount: '0.00',
|
||||
dispatch_count: 0,
|
||||
dispatch_amount: '0.00',
|
||||
};
|
||||
|
||||
export async function fetchMerchantOrderStats(request, params = {}) {
|
||||
const statsUrl = isStaffMode() ? STAFF_API.orderStats : '/dingdan/sjshujutj';
|
||||
try {
|
||||
const res = await request({ url: statsUrl, method: 'POST', data: params });
|
||||
if (res.statusCode === 200 && res.data && (res.data.code === 0 || res.data.code === 200)) {
|
||||
return res.data.data || {};
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('统计接口失败,尝试回退', e);
|
||||
}
|
||||
|
||||
try {
|
||||
const listUrl = isStaffMode() ? STAFF_API.orderList : '/dingdan/sjdingdanhq';
|
||||
const fb = await request({
|
||||
url: listUrl,
|
||||
method: 'POST',
|
||||
data: { zhuangtai_list: [8], page: 1, page_size: 1, ...params },
|
||||
});
|
||||
if (fb.statusCode === 200 && fb.data && fb.data.code === 0) {
|
||||
const d = fb.data.data || {};
|
||||
return {
|
||||
order: {
|
||||
...EMPTY_ORDER,
|
||||
pending_count: d.pending_count || 0,
|
||||
pending_amount: d.pending_amount || '0.00',
|
||||
},
|
||||
can_view_finance: true,
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('统计回退失败', e);
|
||||
}
|
||||
return { order: { ...EMPTY_ORDER }, can_view_finance: true };
|
||||
}
|
||||
|
||||
export function applyOrderStatsToPage(page, data) {
|
||||
const order = data.order || data || {};
|
||||
const penalty = data.penalty || {};
|
||||
const fakuanP = penalty.fakuan_pending || 0;
|
||||
const jifenP = penalty.jifen_pending || 0;
|
||||
page.setData({
|
||||
canViewFinance: data.can_view_finance !== false,
|
||||
orderStats: {
|
||||
pending_count: order.pending_count || 0,
|
||||
pending_amount: order.pending_amount || '0.00',
|
||||
completed_count: order.completed_count || 0,
|
||||
completed_amount: order.completed_amount || '0.00',
|
||||
refund_count: order.refund_count || 0,
|
||||
refund_amount: order.refund_amount || '0.00',
|
||||
dispatch_count: order.dispatch_count || 0,
|
||||
dispatch_amount: order.dispatch_amount || '0.00',
|
||||
},
|
||||
penaltyStats: {
|
||||
fakuan_pending: fakuanP,
|
||||
fakuan_pending_amount: penalty.fakuan_pending_amount || '0.00',
|
||||
jifen_pending: jifenP,
|
||||
},
|
||||
pendingCount: order.pending_count || 0,
|
||||
punishPending: fakuanP + jifenP,
|
||||
});
|
||||
}
|
||||
|
||||
export function applyOrderStatsToHome(page, data, statsKey = 'stats') {
|
||||
const order = data.order || data || {};
|
||||
const base = page.data[statsKey] || {};
|
||||
page.setData({
|
||||
[statsKey]: {
|
||||
...base,
|
||||
pendingCount: order.pending_count || 0,
|
||||
pendingAmount: order.pending_amount || '0.00',
|
||||
completedCount: order.completed_count || 0,
|
||||
completedAmount: order.completed_amount || '0.00',
|
||||
refundCount: order.refund_count || 0,
|
||||
refundAmount: order.refund_amount || '0.00',
|
||||
dispatchCount: order.dispatch_count || 0,
|
||||
dispatchAmount: order.dispatch_amount || '0.00',
|
||||
},
|
||||
});
|
||||
}
|
||||
183
utils/miniapp-icons.js
Normal file
183
utils/miniapp-icons.js
Normal file
@@ -0,0 +1,183 @@
|
||||
/** 小程序可配置图标(启动时从 qdpzhq.miniappAssets 注入 globalData) */
|
||||
|
||||
import { buildClubHeaders } from './club-context.js';
|
||||
|
||||
export const ICON_KEYS = {
|
||||
MERCHANT_GOLD_BANNER: 'merchant_gold_banner',
|
||||
MERCHANT_HOME_REGULAR: 'merchant_home_regular',
|
||||
MERCHANT_HOME_CUSTOM: 'merchant_home_custom',
|
||||
MERCHANT_HOME_LINK: 'merchant_home_link',
|
||||
MERCHANT_HOME_KEFU_KEY: 'merchant_home_kefu_key',
|
||||
MERCHANT_HOME_KEFU_LIST: 'merchant_home_kefu_list',
|
||||
MERCHANT_HOME_NOTICE: 'merchant_home_notice',
|
||||
MERCHANT_HOME_STAT_BG: 'merchant_home_stat_bg',
|
||||
MERCHANT_REGULAR: 'merchant_regular_dispatch',
|
||||
MERCHANT_CUSTOM: 'merchant_custom_dispatch',
|
||||
MERCHANT_PENDING: 'merchant_pending_settle',
|
||||
MERCHANT_KEFU_KEY: 'merchant_kefu_key',
|
||||
MERCHANT_KEFU_LIST: 'merchant_kefu_list',
|
||||
MERCHANT_LINK: 'merchant_link_dispatch',
|
||||
MERCHANT_PUNISH: 'merchant_punish',
|
||||
MERCHANT_RANK: 'merchant_rank',
|
||||
MERCHANT_REFRESH: 'merchant_refresh',
|
||||
FIGHTER_RECHARGE_MEMBER: 'fighter_recharge_member',
|
||||
FIGHTER_RECHARGE_DEPOSIT: 'fighter_recharge_deposit',
|
||||
MINE_PINDAO: 'mine_pindao',
|
||||
ICON_REFRESH: 'icon_refresh',
|
||||
};
|
||||
|
||||
/** 仅后台上传后才展示,不使用 CDN/本地兜底 */
|
||||
export const CONFIG_ONLY_ICON_KEYS = new Set([
|
||||
ICON_KEYS.FIGHTER_RECHARGE_MEMBER,
|
||||
ICON_KEYS.FIGHTER_RECHARGE_DEPOSIT,
|
||||
]);
|
||||
|
||||
/** 逍遥梦原版金牌横幅(硬编码时代 82.png) */
|
||||
export const MERCHANT_GOLD_BANNER_FALLBACK = 'https://bintao.xmxym88.com/xcx/bintao/82.png';
|
||||
|
||||
/** 频道入口图标默认相对路径(拼接 ossImageUrl;后台上传 mine_pindao 后覆盖) */
|
||||
export const PINDAO_ICON_FALLBACK = 'beijing/tubiao/grzx_pindao.jpg';
|
||||
|
||||
function ossBase(app) {
|
||||
return (app && app.globalData && app.globalData.ossImageUrl) || '';
|
||||
}
|
||||
|
||||
export function applyMiniappAssetsFromConfig(app, config) {
|
||||
if (!app || !config) return;
|
||||
const assets = config.miniappAssets || {};
|
||||
app.globalData.miniappIcons = assets.icons || {};
|
||||
app.globalData.pindaoConfig = assets.pindao || { channel_no: '', title: '频道', images: [] };
|
||||
app.globalData.posterConfig = assets.poster || {
|
||||
guanshi_bg: 'beijing/haibaobeijing.jpg',
|
||||
zuzhang_bg: 'beijing/zuzhangbeijing.jpg',
|
||||
};
|
||||
}
|
||||
|
||||
/** 后台已上传的可配置图标(无则返回空,不显示) */
|
||||
export function resolveConfiguredIcon(app, key) {
|
||||
const icons = (app && app.globalData && app.globalData.miniappIcons) || {};
|
||||
const path = icons[key];
|
||||
if (!path) return '';
|
||||
if (path.startsWith('http')) return path;
|
||||
return ossBase(app) + path;
|
||||
}
|
||||
|
||||
/** 通用图标:有配置用配置,否则用 fallback */
|
||||
export function resolveMiniappIcon(app, key, fallback = '') {
|
||||
if (CONFIG_ONLY_ICON_KEYS.has(key)) {
|
||||
return resolveConfiguredIcon(app, key);
|
||||
}
|
||||
const icons = (app && app.globalData && app.globalData.miniappIcons) || {};
|
||||
const path = icons[key];
|
||||
if (!path) return fallback;
|
||||
if (path.startsWith('http')) return path;
|
||||
return ossBase(app) + path;
|
||||
}
|
||||
|
||||
export function getPindaoConfig(app) {
|
||||
return (app && app.globalData && app.globalData.pindaoConfig) || {
|
||||
channel_no: '',
|
||||
title: '频道',
|
||||
images: [],
|
||||
};
|
||||
}
|
||||
|
||||
export function hasPindaoEntry(app) {
|
||||
const cfg = getPindaoConfig(app);
|
||||
if ((cfg.channel_no || '').trim()) return true;
|
||||
return (cfg.images || []).length > 0;
|
||||
}
|
||||
|
||||
export function resolvePindaoImages(app) {
|
||||
const cfg = getPindaoConfig(app);
|
||||
const base = ossBase(app);
|
||||
return (cfg.images || []).map((url) => {
|
||||
if (!url) return '';
|
||||
return url.startsWith('http') ? url : base + url;
|
||||
}).filter(Boolean);
|
||||
}
|
||||
|
||||
/** 预加载频道图片,减少弹窗打开后等待 */
|
||||
export function preloadPindaoImages(urls) {
|
||||
(urls || []).forEach((src) => {
|
||||
if (!src) return;
|
||||
wx.getImageInfo({ src, fail: () => {} });
|
||||
});
|
||||
}
|
||||
|
||||
/** 打开频道前拉取最新 qdpzhq 配置(避免本地缓存无 pindao 数据) */
|
||||
export async function refreshPindaoConfig(app) {
|
||||
app = app || getApp();
|
||||
try {
|
||||
if (typeof app.fetchConfigSafely === 'function') {
|
||||
const config = await app.fetchConfigSafely();
|
||||
if (config && typeof config === 'object') {
|
||||
if (typeof app.applyDynamicConfig === 'function') {
|
||||
app.applyDynamicConfig(config);
|
||||
} else {
|
||||
applyMiniappAssetsFromConfig(app, config);
|
||||
}
|
||||
if (typeof app.saveConfigToStorage === 'function') {
|
||||
app.saveConfigToStorage(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('refreshPindaoConfig failed', e);
|
||||
}
|
||||
const cfg = getPindaoConfig(app);
|
||||
const images = resolvePindaoImages(app);
|
||||
preloadPindaoImages(images);
|
||||
return { cfg, images };
|
||||
}
|
||||
|
||||
/** 后台静默预热频道(不打断 UI) */
|
||||
export function warmupPindaoConfig(app) {
|
||||
const cached = resolvePindaoImages(app || getApp());
|
||||
if (cached.length) preloadPindaoImages(cached);
|
||||
refreshPindaoConfig(app).catch(() => {});
|
||||
}
|
||||
|
||||
export function getPosterConfig(app) {
|
||||
return (app && app.globalData && app.globalData.posterConfig) || {
|
||||
guanshi_bg: 'beijing/haibaobeijing.jpg',
|
||||
zuzhang_bg: 'beijing/zuzhangbeijing.jpg',
|
||||
};
|
||||
}
|
||||
|
||||
export function resolvePosterBg(app, role) {
|
||||
const cfg = getPosterConfig(app);
|
||||
const rel = role === 'zuzhang' ? cfg.zuzhang_bg : cfg.guanshi_bg;
|
||||
const base = ossBase(app);
|
||||
const fallback = role === 'zuzhang' ? 'beijing/zuzhangbeijing.jpg' : 'beijing/haibaobeijing.jpg';
|
||||
const path = rel || fallback;
|
||||
return path.startsWith('http') ? path : base + path;
|
||||
}
|
||||
|
||||
/** 进入海报页拉取当前俱乐部背景配置 */
|
||||
export async function refreshPosterConfig(app) {
|
||||
app = app || getApp();
|
||||
try {
|
||||
const token = wx.getStorageSync('token');
|
||||
const header = buildClubHeaders({ 'Content-Type': 'application/json' });
|
||||
if (token) header.Authorization = 'Bearer ' + token;
|
||||
const res = await new Promise((resolve, reject) => {
|
||||
wx.request({
|
||||
url: app.globalData.apiBaseUrl + '/peizhi/haibaopz',
|
||||
method: 'POST',
|
||||
header,
|
||||
success: (r) => resolve(r),
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
const body = res?.data;
|
||||
if (body && body.code === 0 && body.data && body.data.poster) {
|
||||
app.globalData.posterConfig = body.data.poster;
|
||||
if (body.data.club_id) app.globalData.clubId = body.data.club_id;
|
||||
return body.data.poster;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('refreshPosterConfig failed', e);
|
||||
}
|
||||
return getPosterConfig(app);
|
||||
}
|
||||
47
utils/order-tags.js
Normal file
47
utils/order-tags.js
Normal file
@@ -0,0 +1,47 @@
|
||||
/** 订单标签 texiao_json 规范化(兼容字符串 / 对象) */
|
||||
export function parseTexiaoJson(raw) {
|
||||
if (!raw) return {};
|
||||
if (typeof raw === 'object') return { ...raw };
|
||||
if (typeof raw === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(raw);
|
||||
return typeof parsed === 'object' && parsed ? { ...parsed } : {};
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/** 合并顶层 color / flash 与 texiao_json */
|
||||
export function mergeTagTexiao(tag) {
|
||||
const texiao = parseTexiaoJson(tag.texiao_json);
|
||||
const color = tag.color || texiao.color || texiao.bg_color;
|
||||
if (color) {
|
||||
texiao.color = color;
|
||||
}
|
||||
if (tag.flash_enabled != null && texiao.flash == null) {
|
||||
texiao.flash = !!tag.flash_enabled;
|
||||
}
|
||||
return texiao;
|
||||
}
|
||||
|
||||
export function normalizeTagList(list) {
|
||||
if (!Array.isArray(list)) return [];
|
||||
return list.map((tag) => ({
|
||||
...tag,
|
||||
id: tag.id,
|
||||
mingcheng: tag.mingcheng || tag.name || '',
|
||||
texiao_json: mergeTagTexiao(tag),
|
||||
}));
|
||||
}
|
||||
|
||||
export function normalizeOrderTags(item) {
|
||||
return {
|
||||
xuqiu_biaoqian: normalizeTagList(item.xuqiu_biaoqian),
|
||||
dashou_biaoqian: normalizeTagList(item.dashou_biaoqian),
|
||||
shangjia_biaoqian: normalizeTagList(item.shangjia_biaoqian),
|
||||
shangjia_identity_biaoqian: normalizeTagList(item.shangjia_identity_biaoqian),
|
||||
zhiding_identity_biaoqian: normalizeTagList(item.zhiding_identity_biaoqian),
|
||||
};
|
||||
}
|
||||
@@ -1,57 +1,41 @@
|
||||
/**
|
||||
* 手机号强制认证 - 完全自包含逻辑模块
|
||||
*
|
||||
* 特点:
|
||||
* 1. 不引用项目中的 request.js / upload.js,避免其顶层 getApp() 带来的加载顺序错误
|
||||
* 2. 所有请求直接使用 wx.request / wx.uploadFile,基础 URL 写死(与 app.globalData.apiBaseUrl 保持一致)
|
||||
* 3. check() 内部捕获所有异常,永远返回 false 或 true,不会抛出错误
|
||||
* 4. submit() 抛出错误由认证页面自己处理,不影响主流程
|
||||
*
|
||||
* 用法:
|
||||
* app.js 中:
|
||||
* try {
|
||||
* const { check } = require('./utils/phone-auth');
|
||||
* if (await check()) { wx.reLaunch(...); return; }
|
||||
* } catch(e) {}
|
||||
*
|
||||
* 认证页面中:
|
||||
* const { submit } = require('../../utils/phone-auth');
|
||||
* await submit(phoneCode, avatarPath);
|
||||
* 手机号强制认证:每次调用 check / ensurePhoneAuth 均请求后端 /peizhi/yhbdsjh
|
||||
*/
|
||||
|
||||
// ==================== 基础配置 ====================
|
||||
const API_BASE = 'https://www.abas.asia/hqhd'; // 与你 app.js 里的 apiBaseUrl 一致
|
||||
const AUTH_CHECK_URL = '/peizhi/yhbdsjh'; // 检查是否需要认证
|
||||
const AUTH_SUBMIT_URL = '/yonghu/xiugai'; // 提交认证(复用修改信息接口)
|
||||
const API_BASE = 'https://www.abas.asia/hqhd';
|
||||
const AUTH_CHECK_URL = '/peizhi/yhbdsjh';
|
||||
const AUTH_SUBMIT_URL = '/yonghu/xiugai';
|
||||
const AUTH_PAGE = '/pages/phone-auth/phone-auth';
|
||||
const RETURN_ROLE_KEY = 'phone_auth_return_role';
|
||||
const RETURN_PAGE_KEY = 'phone_auth_return_page';
|
||||
|
||||
function buildHeaders(extra = {}) {
|
||||
const token = wx.getStorageSync('token');
|
||||
const header = { 'Content-Type': 'application/json', ...extra };
|
||||
if (token) header['Authorization'] = 'Bearer ' + token;
|
||||
try {
|
||||
const { buildClubHeaders } = require('./club-context.js');
|
||||
return buildClubHeaders(header);
|
||||
} catch (e) {
|
||||
const clubId = wx.getStorageSync('club_id') || 'xq';
|
||||
header['X-Club-Id'] = clubId;
|
||||
return header;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 内部封装:wx.request ====================
|
||||
function innerRequest(url, data = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const token = wx.getStorageSync('token'); // 运行时获取 token,不提前取
|
||||
const header = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
if (token) {
|
||||
header['Authorization'] = 'Bearer ' + token;
|
||||
}
|
||||
|
||||
wx.request({
|
||||
url: API_BASE + url,
|
||||
method: 'POST',
|
||||
data,
|
||||
header,
|
||||
success: (res) => {
|
||||
// 正常响应直接 resolve 整个响应对象,让上层自己解析
|
||||
resolve(res);
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err);
|
||||
}
|
||||
header: buildHeaders(),
|
||||
success: (res) => resolve(res),
|
||||
fail: (err) => reject(err),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 内部封装:wx.uploadFile ====================
|
||||
function innerUpload(url, filePath, formData = {}, fileName = 'avatar') {
|
||||
return new Promise((resolve, reject) => {
|
||||
const token = wx.getStorageSync('token');
|
||||
@@ -60,7 +44,6 @@ function innerUpload(url, filePath, formData = {}, fileName = 'avatar') {
|
||||
return;
|
||||
}
|
||||
|
||||
// 先验证文件是否存在
|
||||
wx.getFileInfo({
|
||||
filePath,
|
||||
success: () => {
|
||||
@@ -69,15 +52,11 @@ function innerUpload(url, filePath, formData = {}, fileName = 'avatar') {
|
||||
filePath,
|
||||
name: fileName,
|
||||
formData,
|
||||
header: {
|
||||
'Authorization': 'Bearer ' + token
|
||||
},
|
||||
header: buildHeaders({ Authorization: 'Bearer ' + token }),
|
||||
success: (res) => {
|
||||
// ✅ 修复:wx.uploadFile 成功时 statusCode 为 200
|
||||
if (res.statusCode === 200) {
|
||||
try {
|
||||
const data = JSON.parse(res.data);
|
||||
resolve(data); // 直接返回解析后的对象,方便下游使用
|
||||
resolve(JSON.parse(res.data));
|
||||
} catch (e) {
|
||||
reject(new Error('解析响应数据失败'));
|
||||
}
|
||||
@@ -88,78 +67,142 @@ function innerUpload(url, filePath, formData = {}, fileName = 'avatar') {
|
||||
reject(new Error(`上传失败,状态码:${res.statusCode}`));
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(new Error('网络请求失败'));
|
||||
}
|
||||
fail: () => reject(new Error('网络请求失败')),
|
||||
});
|
||||
},
|
||||
fail: () => {
|
||||
reject(new Error('文件不存在或无法访问'));
|
||||
}
|
||||
fail: () => reject(new Error('文件不存在或无法访问')),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 公开方法 ====================
|
||||
function isOnPhoneAuthPage() {
|
||||
const pages = getCurrentPages();
|
||||
const cur = pages[pages.length - 1];
|
||||
return !!(cur && cur.route === 'pages/phone-auth/phone-auth');
|
||||
}
|
||||
|
||||
function parseCheckResponse(res) {
|
||||
if (!res || res.statusCode === 401) {
|
||||
return { ok: false, needAuth: false };
|
||||
}
|
||||
if (res.statusCode !== 200) {
|
||||
return { ok: false, needAuth: false };
|
||||
}
|
||||
const body = res.data;
|
||||
if (!body || typeof body !== 'object') {
|
||||
return { ok: false, needAuth: false };
|
||||
}
|
||||
const code = Number(body.code);
|
||||
if (code !== 0 && code !== 200) {
|
||||
return { ok: false, needAuth: false };
|
||||
}
|
||||
const needAuth = body.need_auth === true
|
||||
|| (body.data && body.data.need_auth === true);
|
||||
return { ok: true, needAuth: !!needAuth };
|
||||
}
|
||||
|
||||
/** 根据当前主端/身份推断认证完成后回跳角色 */
|
||||
function resolveReturnRole() {
|
||||
const primary = wx.getStorageSync('primaryRole') || wx.getStorageSync('currentRole') || '';
|
||||
if (primary === 'dashou' || primary === 'shangjia') return primary;
|
||||
if (Number(wx.getStorageSync('shangjiastatus')) === 1) return 'shangjia';
|
||||
if (Number(wx.getStorageSync('staffstatus')) === 1) return 'shangjia';
|
||||
if (
|
||||
Number(wx.getStorageSync('dashoustatus')) === 1
|
||||
|| Number(wx.getStorageSync('guanshistatus')) === 1
|
||||
|| Number(wx.getStorageSync('zuzhangstatus')) === 1
|
||||
|| Number(wx.getStorageSync('kaoheguanstatus')) === 1
|
||||
) {
|
||||
return 'dashou';
|
||||
}
|
||||
return 'mine';
|
||||
}
|
||||
|
||||
function defaultReturnPage(role) {
|
||||
if (role === 'dashou') return '/pages/accept-order/accept-order';
|
||||
if (role === 'shangjia') return '/pages/merchant-home/merchant-home';
|
||||
return '/pages/mine/mine';
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否需要手机号认证
|
||||
* - 无 token 或 任何异常 → 返回 false
|
||||
* - 后端明确 need_auth: true → 返回 true
|
||||
*/
|
||||
async function check() {
|
||||
const token = wx.getStorageSync('token');
|
||||
if (!token) return false;
|
||||
|
||||
try {
|
||||
const res = await innerRequest(AUTH_CHECK_URL, {});
|
||||
// 假设后端返回格式:{ data: { need_auth: true/false } } 或直接在顶层
|
||||
const body = res.data;
|
||||
return body && body.need_auth === true;
|
||||
const parsed = parseCheckResponse(res);
|
||||
if (!parsed.ok) return false;
|
||||
return parsed.needAuth;
|
||||
} catch (e) {
|
||||
// 任何错误(网络、后端异常)都吞掉,不影响主流程
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function redirectToPhoneAuth(returnRole, returnPage) {
|
||||
const role = returnRole || resolveReturnRole();
|
||||
wx.setStorageSync(RETURN_ROLE_KEY, role);
|
||||
if (returnPage) {
|
||||
wx.setStorageSync(RETURN_PAGE_KEY, returnPage);
|
||||
} else {
|
||||
wx.removeStorageSync(RETURN_PAGE_KEY);
|
||||
}
|
||||
if (!isOnPhoneAuthPage()) {
|
||||
wx.reLaunch({ url: AUTH_PAGE });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交认证(手机号 code + 头像文件)
|
||||
* @param {string} phoneCode 微信 getPhoneNumber 返回的 code
|
||||
* @param {string} avatarPath 头像临时路径
|
||||
* @returns {Promise<object>} 后端返回的完整数据
|
||||
* 向后端确认是否需要手机号认证;需要则跳转认证页
|
||||
* @returns {Promise<boolean>} true=无需认证或已满足;false=已跳转认证页
|
||||
*/
|
||||
async function ensurePhoneAuth(options = {}) {
|
||||
const { redirect = true, role, returnPage } = options;
|
||||
const token = wx.getStorageSync('token');
|
||||
if (!token) return true;
|
||||
|
||||
const needAuth = await check();
|
||||
if (!needAuth) return true;
|
||||
if (redirect) {
|
||||
redirectToPhoneAuth(role || resolveReturnRole(), returnPage);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getPhoneAuthReturnPage() {
|
||||
const savedPage = wx.getStorageSync(RETURN_PAGE_KEY);
|
||||
wx.removeStorageSync(RETURN_PAGE_KEY);
|
||||
if (savedPage) {
|
||||
wx.removeStorageSync(RETURN_ROLE_KEY);
|
||||
return savedPage;
|
||||
}
|
||||
const role = wx.getStorageSync(RETURN_ROLE_KEY) || resolveReturnRole();
|
||||
wx.removeStorageSync(RETURN_ROLE_KEY);
|
||||
return defaultReturnPage(role);
|
||||
}
|
||||
|
||||
async function submit(phoneCode, avatarPath) {
|
||||
if (!phoneCode || !avatarPath) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
// 将手机号 code 放入 formData
|
||||
const formData = {
|
||||
shoujihao_code: phoneCode
|
||||
};
|
||||
const data = await innerUpload(AUTH_SUBMIT_URL, avatarPath, {
|
||||
shoujihao_code: phoneCode,
|
||||
});
|
||||
|
||||
// 调用上传
|
||||
const data = await innerUpload(AUTH_SUBMIT_URL, avatarPath, formData);
|
||||
|
||||
// ✅ 修复:正确判断后端返回的 code 字段,后端成功时 code === 0
|
||||
if (data && data.code === 0) {
|
||||
// data.data 包含 { touxiang: '相对路径', phone: '...' }
|
||||
const respData = data.data || data;
|
||||
|
||||
// 存储手机号
|
||||
if (respData.phone) {
|
||||
wx.setStorageSync('phone', respData.phone);
|
||||
}
|
||||
|
||||
// ✅ 修复:存储头像相对路径,与 xiugai 页面处理方式一致
|
||||
if (respData.touxiang) {
|
||||
wx.setStorageSync('touxiang', respData.touxiang);
|
||||
}
|
||||
|
||||
if (respData.phone) wx.setStorageSync('phone', respData.phone);
|
||||
if (respData.touxiang) wx.setStorageSync('touxiang', respData.touxiang);
|
||||
return respData;
|
||||
} else {
|
||||
throw new Error((data && data.msg) || '认证失败');
|
||||
}
|
||||
throw new Error((data && data.msg) || '认证失败');
|
||||
}
|
||||
|
||||
module.exports = { check, submit };
|
||||
module.exports = {
|
||||
check,
|
||||
submit,
|
||||
ensurePhoneAuth,
|
||||
redirectToPhoneAuth,
|
||||
getPhoneAuthReturnPage,
|
||||
resolveReturnRole,
|
||||
};
|
||||
|
||||
30
utils/poster-page.js
Normal file
30
utils/poster-page.js
Normal file
@@ -0,0 +1,30 @@
|
||||
/** 推广海报页:按俱乐部分缓存二维码与背景 */
|
||||
import { getClubId } from './club-context.js';
|
||||
import { resolvePosterBg, refreshPosterConfig } from './miniapp-icons.js';
|
||||
|
||||
export function posterQrCacheKey(role) {
|
||||
const clubId = getClubId();
|
||||
return role === 'zuzhang'
|
||||
? `zuzhang_haibao_url_${clubId}`
|
||||
: `guanshi_haibao_url_${clubId}`;
|
||||
}
|
||||
|
||||
export function posterInviteCacheKey() {
|
||||
return `zuzhang_yaoqingma_${getClubId()}`;
|
||||
}
|
||||
|
||||
export function getFullImageUrl(url, app) {
|
||||
app = app || getApp();
|
||||
if (!url) return '';
|
||||
const oss = app.globalData.ossImageUrl || '';
|
||||
return url.startsWith('http') ? url : oss + url;
|
||||
}
|
||||
|
||||
export async function loadPosterPageConfig(role, app) {
|
||||
app = app || getApp();
|
||||
await refreshPosterConfig(app);
|
||||
return {
|
||||
bgUrl: resolvePosterBg(app, role),
|
||||
qrCacheKey: posterQrCacheKey(role),
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
/** 三端固定:normal | dashou | shangjia,认证后不可回退 */
|
||||
|
||||
import { isStaffMode } from './staff-api.js';
|
||||
import { ensurePhoneAuth } from './phone-auth.js';
|
||||
|
||||
const VALID = ['normal', 'dashou', 'shangjia'];
|
||||
const LEGACY_CENTER_ROLES = ['guanshi', 'zuzhang', 'kaoheguan'];
|
||||
|
||||
@@ -53,6 +56,9 @@ export function getPrimaryRole(app) {
|
||||
if (isRoleStatusActive(wx.getStorageSync('shangjiastatus'))) {
|
||||
return 'shangjia';
|
||||
}
|
||||
if (isStaffMode()) {
|
||||
return 'shangjia';
|
||||
}
|
||||
return 'normal';
|
||||
}
|
||||
|
||||
@@ -87,8 +93,7 @@ export const PRIMARY_DEFAULT_PAGES = {
|
||||
};
|
||||
|
||||
/** 锁定主端并跳转到该端默认首页,同时建立 IM 长连接 */
|
||||
export function enterLockedRole(role, app) {
|
||||
if (!VALID.includes(role) || role === 'normal') return false;
|
||||
function finishEnterLockedRole(role, app) {
|
||||
app = app || getApp();
|
||||
lockPrimaryRole(role, app);
|
||||
wx.reLaunch({ url: PRIMARY_DEFAULT_PAGES[role] });
|
||||
@@ -103,3 +108,14 @@ export function enterLockedRole(role, app) {
|
||||
}, 600);
|
||||
return true;
|
||||
}
|
||||
|
||||
export function enterLockedRole(role, app) {
|
||||
if (!VALID.includes(role) || role === 'normal') return false;
|
||||
if (role === 'dashou' || role === 'shangjia') {
|
||||
ensurePhoneAuth({ redirect: true, role }).then((ok) => {
|
||||
if (ok) finishEnterLockedRole(role, app);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return finishEnterLockedRole(role, app);
|
||||
}
|
||||
|
||||
157
utils/request.js
157
utils/request.js
@@ -1,74 +1,83 @@
|
||||
// utils/request.js
|
||||
const app = getApp();
|
||||
|
||||
function request(options) {
|
||||
const token = wx.getStorageSync('token');
|
||||
|
||||
// 🔥 没有 token 时不发送请求,直接返回一个“空响应”对象,不影响后续代码
|
||||
if (!token) {
|
||||
// 返回一个模拟的响应,状态码为 401,但不会触发任何弹窗或跳转
|
||||
return Promise.resolve({
|
||||
statusCode: 401,
|
||||
data: { code: 401, msg: '未登录' },
|
||||
errMsg: 'request:ok'
|
||||
});
|
||||
}
|
||||
|
||||
const header = {
|
||||
'Content-Type': 'application/json',
|
||||
...options.header,
|
||||
};
|
||||
if (token) {
|
||||
header['Authorization'] = 'Bearer ' + token;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.request({
|
||||
url: app.globalData.apiBaseUrl + options.url,
|
||||
method: options.method,
|
||||
data: options.data || {},
|
||||
header,
|
||||
success: async (res) => {
|
||||
if (res.statusCode === 401 || res.statusCode === 403) {
|
||||
// 注意:这里的弹窗逻辑只会在有 token 且 token 失效时触发
|
||||
// 无 token 的情况已经在上面直接返回,不会走到这里
|
||||
if (app.globalData.loginPromise) {
|
||||
try {
|
||||
await app.globalData.loginPromise;
|
||||
const retryRes = await request(options);
|
||||
resolve(retryRes);
|
||||
} catch (err) {
|
||||
wx.showModal({
|
||||
content: '操作失败,请先登录',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
wx.removeStorageSync('token');
|
||||
wx.switchTab({ url: '/pages/mine/mine' });
|
||||
}
|
||||
},
|
||||
});
|
||||
resolve(res);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
wx.showModal({
|
||||
content: '操作失败,请先登录',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
wx.removeStorageSync('token');
|
||||
wx.reLaunch({ url: '/pages/mine/mine' });
|
||||
}
|
||||
},
|
||||
});
|
||||
resolve(res);
|
||||
return;
|
||||
}
|
||||
resolve(res);
|
||||
},
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default request;
|
||||
// utils/request.js
|
||||
import { buildClubHeaders } from './club-context';
|
||||
|
||||
function request(options) {
|
||||
const app = getApp();
|
||||
const token = wx.getStorageSync('token');
|
||||
|
||||
if (!token) {
|
||||
return Promise.resolve({
|
||||
statusCode: 401,
|
||||
data: { code: 401, msg: '未登录' },
|
||||
errMsg: 'request:ok'
|
||||
});
|
||||
}
|
||||
|
||||
const header = buildClubHeaders({
|
||||
'Content-Type': 'application/json',
|
||||
...options.header,
|
||||
});
|
||||
if (token) {
|
||||
header['Authorization'] = 'Bearer ' + token;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.request({
|
||||
url: app.globalData.apiBaseUrl + options.url,
|
||||
method: options.method || 'GET',
|
||||
data: options.data || {},
|
||||
header,
|
||||
timeout: options.timeout || 15000,
|
||||
success: async (res) => {
|
||||
if (res.statusCode === 401 || res.statusCode === 403) {
|
||||
if (app.globalData.loginPromise) {
|
||||
try {
|
||||
await app.globalData.loginPromise;
|
||||
const retryRes = await request(options);
|
||||
resolve(retryRes);
|
||||
} catch (err) {
|
||||
wx.showModal({
|
||||
content: '操作失败,请先登录',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
wx.removeStorageSync('token');
|
||||
wx.switchTab({ url: '/pages/mine/mine' });
|
||||
}
|
||||
},
|
||||
});
|
||||
resolve(res);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
wx.showModal({
|
||||
content: '操作失败,请先登录',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
wx.removeStorageSync('token');
|
||||
wx.reLaunch({ url: '/pages/mine/mine' });
|
||||
}
|
||||
},
|
||||
});
|
||||
resolve(res);
|
||||
return;
|
||||
}
|
||||
resolve(res);
|
||||
},
|
||||
fail: (err) => {
|
||||
// 网络层失败(超时/域名不可达等),避免未捕获 Promise 触发 MiniProgramError 红屏
|
||||
console.warn('request fail:', options.url, err && err.errMsg);
|
||||
resolve({
|
||||
statusCode: 0,
|
||||
data: {
|
||||
code: 408,
|
||||
msg: (err && err.errMsg) || '网络请求失败',
|
||||
},
|
||||
errMsg: (err && err.errMsg) || 'request:fail',
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default request;
|
||||
|
||||
@@ -1,255 +1,271 @@
|
||||
import { clearPrimaryRole } from './primary-role';
|
||||
import { getLocalImUserId } from './im-user.js';
|
||||
export function isWechatLoginSuccess(body) {
|
||||
if (!body) return false;
|
||||
const code = Number(body.code);
|
||||
return code === 0 || code === 200;
|
||||
}
|
||||
|
||||
const SESSION_STATUS_KEYS = ['dashoustatus', 'guanshistatus', 'shangjiastatus', 'zuzhangstatus', 'kaoheguanstatus'];
|
||||
|
||||
/** 读取 token(Storage 优先,globalData 兜底并回写 Storage) */
|
||||
export function getSessionToken(app) {
|
||||
app = app || getApp();
|
||||
let token = wx.getStorageSync('token') || app.globalData.token || '';
|
||||
if (token && !wx.getStorageSync('token')) {
|
||||
wx.setStorageSync('token', token);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
export function isUserLoggedIn(app) {
|
||||
return !!getSessionToken(app);
|
||||
}
|
||||
|
||||
/** 从 Storage / globalData 汇总展示用用户信息(兼容其他端注册后 nick 未落盘) */
|
||||
export function getUserProfileForDisplay(app) {
|
||||
app = app || getApp();
|
||||
const token = getSessionToken(app);
|
||||
const uid = wx.getStorageSync('uid') || app.globalData.uid || '';
|
||||
let nick = wx.getStorageSync('nicheng') || app.globalData.nicheng || '';
|
||||
if (!nick && app.globalData.dashouNicheng) {
|
||||
nick = app.globalData.dashouNicheng;
|
||||
}
|
||||
const tx = wx.getStorageSync('touxiang') || '';
|
||||
const def = app.globalData.morentouxiang || '';
|
||||
const oss = app.globalData.ossImageUrl || '';
|
||||
let avatarUrl = '';
|
||||
if (tx) {
|
||||
avatarUrl = tx.startsWith('http') ? tx : oss + tx;
|
||||
} else if (def) {
|
||||
avatarUrl = def.startsWith('http') ? def : oss + def;
|
||||
}
|
||||
return {
|
||||
nick,
|
||||
uid,
|
||||
avatarUrl,
|
||||
isLoggedIn: !!token,
|
||||
};
|
||||
}
|
||||
|
||||
/** 其他端登录/注册成功后,把 globalData 里已有字段补写回 Storage */
|
||||
export function backfillUserProfileCache(app) {
|
||||
app = app || getApp();
|
||||
if (!isUserLoggedIn()) return;
|
||||
const profile = getUserProfileForDisplay(app);
|
||||
if (profile.nick && !wx.getStorageSync('nicheng')) {
|
||||
wx.setStorageSync('nicheng', profile.nick);
|
||||
app.globalData.nicheng = profile.nick;
|
||||
}
|
||||
if (profile.uid && !wx.getStorageSync('uid')) {
|
||||
wx.setStorageSync('uid', profile.uid);
|
||||
}
|
||||
}
|
||||
|
||||
export function applyWechatLoginData(data, page) {
|
||||
if (!data || typeof data !== 'object') return;
|
||||
const app = getApp();
|
||||
syncProfileFields(data);
|
||||
if (data.token) {
|
||||
wx.setStorageSync('token', data.token);
|
||||
}
|
||||
syncRoleStatuses(data);
|
||||
syncGroupFields(data);
|
||||
if (data.zuzhangstatus !== undefined) {
|
||||
wx.setStorageSync('zuzhangstatus', data.zuzhangstatus);
|
||||
app.globalData.zuzhangstatus = data.zuzhangstatus;
|
||||
}
|
||||
if (data.kaoheguanstatus !== undefined) {
|
||||
wx.setStorageSync('kaoheguanstatus', data.kaoheguanstatus);
|
||||
app.globalData.kaoheguanstatus = data.kaoheguanstatus;
|
||||
}
|
||||
if (data.dingdantiaoshu) {
|
||||
app.globalData.dingdanTiaoshu = {
|
||||
daifuwu: data.dingdantiaoshu.daifuwu || 0,
|
||||
fuwuzhong: data.dingdantiaoshu.fuwuzhong || 0,
|
||||
yiwancheng: data.dingdantiaoshu.yiwancheng || 0,
|
||||
yituikuan: data.dingdantiaoshu.yituikuan || 0,
|
||||
};
|
||||
}
|
||||
if (data.isJinpai !== undefined) {
|
||||
wx.setStorageSync('isJinpai', Number(data.isJinpai));
|
||||
}
|
||||
|
||||
const targetPage = page || _getCurrentPage();
|
||||
refreshTabBar(targetPage);
|
||||
}
|
||||
|
||||
function _getCurrentPage() {
|
||||
const pages = getCurrentPages();
|
||||
return pages.length ? pages[pages.length - 1] : null;
|
||||
}
|
||||
|
||||
export function isRoleStatusActive(status) {
|
||||
return Number(status) === 1;
|
||||
}
|
||||
|
||||
export function isCenterPageActive(page, dataFlag, statusKey) {
|
||||
return page.data[dataFlag] || isRoleStatusActive(wx.getStorageSync(statusKey));
|
||||
}
|
||||
|
||||
export function syncRoleStatuses(data) {
|
||||
if (!data || typeof data !== 'object') return;
|
||||
const app = getApp();
|
||||
const roleFields = ['dashoustatus', 'guanshistatus', 'shangjiastatus', 'zuzhangstatus', 'kaoheguanstatus'];
|
||||
roleFields.forEach((field) => {
|
||||
if (Object.prototype.hasOwnProperty.call(data, field)) {
|
||||
const val = Number(data[field]);
|
||||
const normalized = Number.isNaN(val) ? 0 : val;
|
||||
wx.setStorageSync(field, normalized);
|
||||
app.globalData[field] = normalized;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function syncProfileFields(data) {
|
||||
if (!data || typeof data !== 'object') return;
|
||||
const app = getApp();
|
||||
['nicheng', 'uid', 'touxiang', 'token'].forEach((field) => {
|
||||
if (data[field] !== undefined && data[field] !== null) {
|
||||
wx.setStorageSync(field, data[field]);
|
||||
app.globalData[field] = data[field];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 清除缓存:清空登录与身份,回到点单端「我的」 */
|
||||
export function clearCacheAndEnterNormal(page) {
|
||||
resetGuestSession(page, { skipPageReset: true });
|
||||
wx.reLaunch({ url: '/pages/mine/mine' });
|
||||
}
|
||||
|
||||
/** 清除缓存并重置为点单端(不跳转,供当前已在点单端时使用) */
|
||||
export function resetGuestSession(page, options = {}) {
|
||||
const app = getApp();
|
||||
const configKey = app.CONFIG_CACHE_KEY || 'app_dynamic_config';
|
||||
let configCache = null;
|
||||
try {
|
||||
configCache = wx.getStorageSync(configKey);
|
||||
} catch (e) {}
|
||||
|
||||
wx.clearStorageSync();
|
||||
|
||||
if (configCache && typeof configCache === 'object') {
|
||||
wx.setStorageSync(configKey, configCache);
|
||||
}
|
||||
|
||||
clearPrimaryRole(app);
|
||||
app.globalData.token = '';
|
||||
app.globalData.nicheng = '';
|
||||
app.globalData.uid = '';
|
||||
app.globalData.loginPromise = null;
|
||||
app.globalData.currentUser = null;
|
||||
app.globalData.dashouNicheng = '';
|
||||
app.globalData.dingdanTiaoshu = { daifuwu: 0, fuwuzhong: 0, yiwancheng: 0, yituikuan: 0 };
|
||||
app.globalData.dashouqun = '';
|
||||
app.globalData.dashouqunid = '';
|
||||
app.globalData.guanshiqun = '';
|
||||
app.globalData.guanshiqunid = '';
|
||||
SESSION_STATUS_KEYS.forEach((key) => {
|
||||
app.globalData[key] = 0;
|
||||
});
|
||||
app.globalData.shangpinleixing = [];
|
||||
app.globalData.shangpinzhuanqu = [];
|
||||
app.globalData.shangpinliebiao = [];
|
||||
|
||||
app.emitEvent('currentRoleChanged', { role: 'normal' });
|
||||
refreshTabBar(page);
|
||||
|
||||
if (!options.skipPageReset && page && page.setData) {
|
||||
page.setData({
|
||||
avatarUrl: '',
|
||||
userNicheng: '',
|
||||
userUid: '',
|
||||
showLoginBtn: true,
|
||||
orderCounts: { daifuwu: 0, fuwuzhong: 0, yiwancheng: 0, yituikuan: 0 },
|
||||
dashouCertified: false,
|
||||
shangjiaCertified: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function syncGroupFields(data) {
|
||||
if (!data || typeof data !== 'object') return;
|
||||
const app = getApp();
|
||||
['dashouqun', 'dashouqunid', 'guanshiqun', 'guanshiqunid'].forEach((field) => {
|
||||
if (data[field] !== undefined) {
|
||||
wx.setStorageSync(field, data[field]);
|
||||
app.globalData[field] = data[field];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function refreshTabBar(page) {
|
||||
const tryRefresh = () => {
|
||||
const tabBar = page.getTabBar && page.getTabBar();
|
||||
if (tabBar && tabBar.refresh) tabBar.refresh();
|
||||
};
|
||||
tryRefresh();
|
||||
[50, 150, 300, 500, 800].forEach((delay) => setTimeout(tryRefresh, delay));
|
||||
}
|
||||
|
||||
export function setRoleAndRefreshTabBar(page, role) {
|
||||
const app = getApp();
|
||||
if (app.setCurrentRole) {
|
||||
app.setCurrentRole(role);
|
||||
} else {
|
||||
app.globalData.currentRole = role;
|
||||
wx.setStorageSync('currentRole', role);
|
||||
app.emitEvent('currentRoleChanged', { role });
|
||||
}
|
||||
refreshTabBar(page);
|
||||
}
|
||||
|
||||
export function reconnectForRole(role) {
|
||||
const app = getApp();
|
||||
if (!wx.getStorageSync('uid')) return;
|
||||
|
||||
const targetUserId = getLocalImUserId(app);
|
||||
if (!targetUserId) return;
|
||||
|
||||
const status = wx.goEasy?.getConnectionStatus?.() || 'disconnected';
|
||||
const currentUserId = wx.goEasy?.im?.userId || '';
|
||||
const connected = status === 'connected' || status === 'reconnected';
|
||||
|
||||
if (connected && currentUserId === targetUserId) {
|
||||
if (app.loadConversations) app.loadConversations();
|
||||
if (app.ensureConnection) app.ensureConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
if (app.switchRoleAndReconnect) {
|
||||
app.switchRoleAndReconnect(role).catch(() => {
|
||||
if (app.ensureConnection) app.ensureConnection();
|
||||
});
|
||||
} else if (app.connectForCurrentRole) {
|
||||
app.connectForCurrentRole();
|
||||
} else if (app.ensureConnection) {
|
||||
app.ensureConnection();
|
||||
}
|
||||
}
|
||||
|
||||
export function ensureRoleOnCenterPage(page, role) {
|
||||
setRoleAndRefreshTabBar(page, role);
|
||||
reconnectForRole(role);
|
||||
}
|
||||
import { clearPrimaryRole } from './primary-role';
|
||||
import { getLocalImUserId } from './im-user.js';
|
||||
import { applyStaffFromLoginData, isStaffMode, restoreStaffContextAfterAuth, clearStaffSession } from './staff-api.js';
|
||||
export function isWechatLoginSuccess(body) {
|
||||
if (!body) return false;
|
||||
const code = Number(body.code);
|
||||
return code === 0 || code === 200;
|
||||
}
|
||||
|
||||
const SESSION_STATUS_KEYS = ['dashoustatus', 'guanshistatus', 'shangjiastatus', 'zuzhangstatus', 'kaoheguanstatus'];
|
||||
|
||||
/** 读取 token(Storage 优先,globalData 兜底并回写 Storage) */
|
||||
export function getSessionToken(app) {
|
||||
app = app || getApp();
|
||||
let token = wx.getStorageSync('token') || app.globalData.token || '';
|
||||
if (token && !wx.getStorageSync('token')) {
|
||||
wx.setStorageSync('token', token);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
export function isUserLoggedIn(app) {
|
||||
return !!getSessionToken(app);
|
||||
}
|
||||
|
||||
/** 从 Storage / globalData 汇总展示用用户信息(兼容其他端注册后 nick 未落盘) */
|
||||
export function getUserProfileForDisplay(app) {
|
||||
app = app || getApp();
|
||||
const token = getSessionToken(app);
|
||||
const uid = wx.getStorageSync('uid') || app.globalData.uid || '';
|
||||
let nick = wx.getStorageSync('nicheng') || app.globalData.nicheng || '';
|
||||
if (!nick && app.globalData.dashouNicheng) {
|
||||
nick = app.globalData.dashouNicheng;
|
||||
}
|
||||
const tx = wx.getStorageSync('touxiang') || '';
|
||||
const def = app.globalData.morentouxiang || '';
|
||||
const oss = app.globalData.ossImageUrl || '';
|
||||
let avatarUrl = '';
|
||||
if (tx) {
|
||||
avatarUrl = tx.startsWith('http') ? tx : oss + tx;
|
||||
} else if (def) {
|
||||
avatarUrl = def.startsWith('http') ? def : oss + def;
|
||||
}
|
||||
return {
|
||||
nick,
|
||||
uid,
|
||||
avatarUrl,
|
||||
isLoggedIn: !!token,
|
||||
};
|
||||
}
|
||||
|
||||
/** 其他端登录/注册成功后,把 globalData 里已有字段补写回 Storage */
|
||||
export function backfillUserProfileCache(app) {
|
||||
app = app || getApp();
|
||||
if (!isUserLoggedIn()) return;
|
||||
const profile = getUserProfileForDisplay(app);
|
||||
if (profile.nick && !wx.getStorageSync('nicheng')) {
|
||||
wx.setStorageSync('nicheng', profile.nick);
|
||||
app.globalData.nicheng = profile.nick;
|
||||
}
|
||||
if (profile.uid && !wx.getStorageSync('uid')) {
|
||||
wx.setStorageSync('uid', profile.uid);
|
||||
}
|
||||
}
|
||||
|
||||
export function applyWechatLoginData(data, page) {
|
||||
if (!data || typeof data !== 'object') return;
|
||||
const app = getApp();
|
||||
syncProfileFields(data);
|
||||
if (data.token) {
|
||||
wx.setStorageSync('token', data.token);
|
||||
}
|
||||
syncRoleStatuses(data);
|
||||
syncGroupFields(data);
|
||||
if (data.zuzhangstatus !== undefined) {
|
||||
wx.setStorageSync('zuzhangstatus', data.zuzhangstatus);
|
||||
app.globalData.zuzhangstatus = data.zuzhangstatus;
|
||||
}
|
||||
if (data.kaoheguanstatus !== undefined) {
|
||||
wx.setStorageSync('kaoheguanstatus', data.kaoheguanstatus);
|
||||
app.globalData.kaoheguanstatus = data.kaoheguanstatus;
|
||||
}
|
||||
if (data.dingdantiaoshu) {
|
||||
app.globalData.dingdanTiaoshu = {
|
||||
daifuwu: data.dingdantiaoshu.daifuwu || 0,
|
||||
fuwuzhong: data.dingdantiaoshu.fuwuzhong || 0,
|
||||
yiwancheng: data.dingdantiaoshu.yiwancheng || 0,
|
||||
yituikuan: data.dingdantiaoshu.yituikuan || 0,
|
||||
};
|
||||
}
|
||||
if (data.isJinpai !== undefined) {
|
||||
wx.setStorageSync('isJinpai', Number(data.isJinpai));
|
||||
}
|
||||
|
||||
applyStaffFromLoginData(data);
|
||||
|
||||
const targetPage = page || _getCurrentPage();
|
||||
refreshTabBar(targetPage);
|
||||
restoreStaffContextAfterAuth().finally(() => {
|
||||
refreshTabBar(targetPage);
|
||||
if (app.emitEvent) {
|
||||
app.emitEvent('staffContextChanged', {});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _getCurrentPage() {
|
||||
const pages = getCurrentPages();
|
||||
return pages.length ? pages[pages.length - 1] : null;
|
||||
}
|
||||
|
||||
export function isRoleStatusActive(status) {
|
||||
return Number(status) === 1;
|
||||
}
|
||||
|
||||
export function isCenterPageActive(page, dataFlag, statusKey) {
|
||||
return page.data[dataFlag] || isRoleStatusActive(wx.getStorageSync(statusKey)) || isStaffMode();
|
||||
}
|
||||
|
||||
export function syncRoleStatuses(data) {
|
||||
if (!data || typeof data !== 'object') return;
|
||||
const app = getApp();
|
||||
const roleFields = ['dashoustatus', 'guanshistatus', 'shangjiastatus', 'zuzhangstatus', 'kaoheguanstatus'];
|
||||
roleFields.forEach((field) => {
|
||||
if (Object.prototype.hasOwnProperty.call(data, field)) {
|
||||
const val = Number(data[field]);
|
||||
const normalized = Number.isNaN(val) ? 0 : val;
|
||||
wx.setStorageSync(field, normalized);
|
||||
app.globalData[field] = normalized;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function syncProfileFields(data) {
|
||||
if (!data || typeof data !== 'object') return;
|
||||
const app = getApp();
|
||||
['nicheng', 'uid', 'touxiang', 'token'].forEach((field) => {
|
||||
if (data[field] !== undefined && data[field] !== null) {
|
||||
wx.setStorageSync(field, data[field]);
|
||||
app.globalData[field] = data[field];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 清除缓存:清空登录与身份,回到点单端「我的」 */
|
||||
export function clearCacheAndEnterNormal(page) {
|
||||
resetGuestSession(page, { skipPageReset: true });
|
||||
wx.reLaunch({ url: '/pages/mine/mine' });
|
||||
}
|
||||
|
||||
/** 清除缓存并重置为点单端(不跳转,供当前已在点单端时使用) */
|
||||
export function resetGuestSession(page, options = {}) {
|
||||
const app = getApp();
|
||||
const configKey = app.CONFIG_CACHE_KEY || 'app_dynamic_config';
|
||||
let configCache = null;
|
||||
try {
|
||||
configCache = wx.getStorageSync(configKey);
|
||||
} catch (e) {}
|
||||
|
||||
wx.clearStorageSync();
|
||||
|
||||
clearStaffSession();
|
||||
|
||||
if (configCache && typeof configCache === 'object') {
|
||||
wx.setStorageSync(configKey, configCache);
|
||||
}
|
||||
|
||||
clearPrimaryRole(app);
|
||||
app.globalData.token = '';
|
||||
app.globalData.nicheng = '';
|
||||
app.globalData.uid = '';
|
||||
app.globalData.loginPromise = null;
|
||||
app.globalData.currentUser = null;
|
||||
app.globalData.dashouNicheng = '';
|
||||
app.globalData.dingdanTiaoshu = { daifuwu: 0, fuwuzhong: 0, yiwancheng: 0, yituikuan: 0 };
|
||||
app.globalData.dashouqun = '';
|
||||
app.globalData.dashouqunid = '';
|
||||
app.globalData.guanshiqun = '';
|
||||
app.globalData.guanshiqunid = '';
|
||||
SESSION_STATUS_KEYS.forEach((key) => {
|
||||
app.globalData[key] = 0;
|
||||
});
|
||||
app.globalData.shangpinleixing = [];
|
||||
app.globalData.shangpinzhuanqu = [];
|
||||
app.globalData.shangpinliebiao = [];
|
||||
app.globalData._acceptOrderSessionReady = false;
|
||||
app.globalData._acceptOrderPageCache = null;
|
||||
app.globalData._acceptOrderPopupDone = false;
|
||||
app.globalData._dashouPhoneChecked = false;
|
||||
app.globalData._shangjiaPhoneChecked = false;
|
||||
|
||||
app.emitEvent('currentRoleChanged', { role: 'normal' });
|
||||
refreshTabBar(page);
|
||||
|
||||
if (!options.skipPageReset && page && page.setData) {
|
||||
page.setData({
|
||||
avatarUrl: '',
|
||||
userNicheng: '',
|
||||
userUid: '',
|
||||
showLoginBtn: true,
|
||||
orderCounts: { daifuwu: 0, fuwuzhong: 0, yiwancheng: 0, yituikuan: 0 },
|
||||
dashouCertified: false,
|
||||
shangjiaCertified: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function syncGroupFields(data) {
|
||||
if (!data || typeof data !== 'object') return;
|
||||
const app = getApp();
|
||||
['dashouqun', 'dashouqunid', 'guanshiqun', 'guanshiqunid'].forEach((field) => {
|
||||
if (data[field] !== undefined) {
|
||||
wx.setStorageSync(field, data[field]);
|
||||
app.globalData[field] = data[field];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function refreshTabBar(page) {
|
||||
const tryRefresh = () => {
|
||||
const tabBar = page.getTabBar && page.getTabBar();
|
||||
if (tabBar && tabBar.refresh) tabBar.refresh();
|
||||
};
|
||||
tryRefresh();
|
||||
[50, 150, 300, 500, 800].forEach((delay) => setTimeout(tryRefresh, delay));
|
||||
}
|
||||
|
||||
export function setRoleAndRefreshTabBar(page, role) {
|
||||
const app = getApp();
|
||||
if (app.setCurrentRole) {
|
||||
app.setCurrentRole(role);
|
||||
} else {
|
||||
app.globalData.currentRole = role;
|
||||
wx.setStorageSync('currentRole', role);
|
||||
app.emitEvent('currentRoleChanged', { role });
|
||||
}
|
||||
refreshTabBar(page);
|
||||
}
|
||||
|
||||
export function reconnectForRole(role) {
|
||||
const app = getApp();
|
||||
if (!wx.getStorageSync('uid')) return;
|
||||
|
||||
const targetUserId = getLocalImUserId(app);
|
||||
if (!targetUserId) return;
|
||||
|
||||
const status = wx.goEasy?.getConnectionStatus?.() || 'disconnected';
|
||||
const currentUserId = wx.goEasy?.im?.userId || '';
|
||||
const connected = status === 'connected' || status === 'reconnected';
|
||||
|
||||
if (connected && currentUserId === targetUserId) {
|
||||
if (app.loadConversations) app.loadConversations();
|
||||
if (app.ensureConnection) app.ensureConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
if (app.switchRoleAndReconnect) {
|
||||
app.switchRoleAndReconnect(role).catch(() => {
|
||||
if (app.ensureConnection) app.ensureConnection();
|
||||
});
|
||||
} else if (app.connectForCurrentRole) {
|
||||
app.connectForCurrentRole();
|
||||
} else if (app.ensureConnection) {
|
||||
app.ensureConnection();
|
||||
}
|
||||
}
|
||||
|
||||
export function ensureRoleOnCenterPage(page, role) {
|
||||
setRoleAndRefreshTabBar(page, role);
|
||||
reconnectForRole(role);
|
||||
}
|
||||
|
||||
255
utils/staff-api.js
Normal file
255
utils/staff-api.js
Normal file
@@ -0,0 +1,255 @@
|
||||
/**
|
||||
* 商家子客服 API 与身份上下文
|
||||
*
|
||||
* 身份说明(互斥,不冲突):
|
||||
* - 商家老板:shangjiastatus=1,走原商家接口,扣商家余额
|
||||
* - 子客服(含总管/财务/派单员):staff_context.active,走 merchant-staff 接口,扣个人额度
|
||||
* - 同账号不能既是老板又是子客服;换角色只改权限,额度/统计仍归属同一 member 记录
|
||||
*/
|
||||
import { isApiSuccess } from './api-helper.js';
|
||||
|
||||
const STAFF_PREFIX = '/yonghu/merchant-staff';
|
||||
|
||||
export const STAFF_API = {
|
||||
me: `${STAFF_PREFIX}/me`,
|
||||
bind: `${STAFF_PREFIX}/bind`,
|
||||
inviteCreate: `${STAFF_PREFIX}/invite/create`,
|
||||
inviteList: `${STAFF_PREFIX}/invite/list`,
|
||||
inviteRevoke: `${STAFF_PREFIX}/invite/revoke`,
|
||||
memberList: `${STAFF_PREFIX}/member/list`,
|
||||
memberUpdateRole: `${STAFF_PREFIX}/member/update-role`,
|
||||
memberRemove: `${STAFF_PREFIX}/member/remove`,
|
||||
memberDisable: `${STAFF_PREFIX}/member/disable`,
|
||||
roleList: `${STAFF_PREFIX}/role/list`,
|
||||
roleSetPerms: `${STAFF_PREFIX}/role/set-perms`,
|
||||
walletAllocate: `${STAFF_PREFIX}/wallet/allocate`,
|
||||
walletRevoke: `${STAFF_PREFIX}/wallet/revoke`,
|
||||
walletLedger: `${STAFF_PREFIX}/wallet/ledger`,
|
||||
auditList: `${STAFF_PREFIX}/audit/list`,
|
||||
rank: `${STAFF_PREFIX}/rank`,
|
||||
roleCreate: `${STAFF_PREFIX}/role/create`,
|
||||
roleSetPerms: `${STAFF_PREFIX}/role/set-perms`,
|
||||
memberUpdatePerms: `${STAFF_PREFIX}/member/update-perms`,
|
||||
templateList: `${STAFF_PREFIX}/template/list`,
|
||||
templateCreate: `${STAFF_PREFIX}/template/create`,
|
||||
templateUpdate: `${STAFF_PREFIX}/template/update`,
|
||||
templateDelete: `${STAFF_PREFIX}/template/delete`,
|
||||
linkGenerate: `${STAFF_PREFIX}/link/generate`,
|
||||
linkList: `${STAFF_PREFIX}/link/list`,
|
||||
orderList: `${STAFF_PREFIX}/order/list`,
|
||||
orderDetail: `${STAFF_PREFIX}/order/detail`,
|
||||
orderDispatch: `${STAFF_PREFIX}/order/dispatch`,
|
||||
productTypes: `${STAFF_PREFIX}/order/product-types`,
|
||||
orderSettle: `${STAFF_PREFIX}/order/settle`,
|
||||
orderCancel: `${STAFF_PREFIX}/order/cancel`,
|
||||
orderRefund: `${STAFF_PREFIX}/order/refund`,
|
||||
penaltyApply: `${STAFF_PREFIX}/order/penalty-apply`,
|
||||
penaltyManage: `${STAFF_PREFIX}/order/penalty-manage`,
|
||||
changePlayer: `${STAFF_PREFIX}/order/change-player`,
|
||||
orderStats: `${STAFF_PREFIX}/order/stats`,
|
||||
memberUpdateRemark: `${STAFF_PREFIX}/member/update-remark`,
|
||||
};
|
||||
|
||||
export function getStaffContext() {
|
||||
return wx.getStorageSync('staff_context') || null;
|
||||
}
|
||||
|
||||
export function setStaffContext(ctx) {
|
||||
if (ctx && ctx.active) {
|
||||
wx.setStorageSync('staff_context', ctx);
|
||||
wx.setStorageSync('staffstatus', 1);
|
||||
} else {
|
||||
wx.removeStorageSync('staff_context');
|
||||
wx.setStorageSync('staffstatus', 0);
|
||||
}
|
||||
}
|
||||
|
||||
export function isStaffMode() {
|
||||
// 商家老板与子客服互斥:已认证商家时一律按老板身份
|
||||
if (Number(wx.getStorageSync('shangjiastatus')) === 1) {
|
||||
return false;
|
||||
}
|
||||
const ctx = getStaffContext();
|
||||
if (ctx && ctx.active) {
|
||||
return true;
|
||||
}
|
||||
// 绑定/登录已写入 staffstatus,context 异步刷新期间仍视为客服
|
||||
return Number(wx.getStorageSync('staffstatus')) === 1;
|
||||
}
|
||||
|
||||
export function clearStaffSession() {
|
||||
wx.removeStorageSync('staff_context');
|
||||
wx.setStorageSync('staffstatus', 0);
|
||||
}
|
||||
|
||||
/** 是否仅为商家老板(非子客服) */
|
||||
export function isMerchantOwnerOnly() {
|
||||
return Number(wx.getStorageSync('shangjiastatus')) === 1 && !isStaffMode();
|
||||
}
|
||||
|
||||
/** 子客服/非老板禁止进入商家充值提现等页面 */
|
||||
export function guardMerchantOwnerAction(msg) {
|
||||
if (isMerchantOwnerOnly()) return true;
|
||||
wx.showToast({
|
||||
title: msg || '仅商家老板可充值或提现,子客服请联系老板',
|
||||
icon: 'none',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
export function staffHasPerm(code) {
|
||||
const ctx = getStaffContext();
|
||||
if (!ctx || !ctx.permissions) return false;
|
||||
return ctx.permissions.indexOf(code) >= 0;
|
||||
}
|
||||
|
||||
function loadRequest() {
|
||||
// 延迟加载,避免 App 未注册时 request 模块被提前初始化
|
||||
return require('./request.js').default;
|
||||
}
|
||||
|
||||
export async function refreshStaffContext(req) {
|
||||
const cached = getStaffContext();
|
||||
const token = wx.getStorageSync('token');
|
||||
if (!token) return null;
|
||||
const doRequest = req || loadRequest();
|
||||
try {
|
||||
const res = await doRequest({ url: STAFF_API.me, method: 'POST' });
|
||||
const body = res.data || {};
|
||||
if (res.statusCode === 401) {
|
||||
return cached;
|
||||
}
|
||||
if (isApiSuccess(body) && body.data && body.data.active) {
|
||||
setStaffContext(body.data);
|
||||
return body.data;
|
||||
}
|
||||
if (body.code === 404 || body.code === 403) {
|
||||
// 已有本地客服身份时勿清空,避免误入「商家入驻」
|
||||
if (cached && cached.active) {
|
||||
return cached;
|
||||
}
|
||||
if (Number(wx.getStorageSync('staffstatus')) === 1) {
|
||||
return cached;
|
||||
}
|
||||
clearStaffSession();
|
||||
return null;
|
||||
}
|
||||
return cached;
|
||||
} catch (e) {
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
|
||||
/** 登录/注册响应写入子客服身份 */
|
||||
export function applyStaffFromLoginData(data) {
|
||||
if (!data || typeof data !== 'object') return;
|
||||
if (data.staff_context && data.staff_context.active) {
|
||||
setStaffContext(data.staff_context);
|
||||
return;
|
||||
}
|
||||
if (Number(data.staffstatus) === 1) {
|
||||
wx.setStorageSync('staffstatus', 1);
|
||||
return;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(data, 'staffstatus') && Number(data.staffstatus) !== 1) {
|
||||
clearStaffSession();
|
||||
}
|
||||
}
|
||||
|
||||
/** 登录成功后从 /me 拉取最新子客服身份 */
|
||||
export async function restoreStaffContextAfterAuth() {
|
||||
const token = wx.getStorageSync('token');
|
||||
if (!token) return null;
|
||||
return refreshStaffContext(loadRequest());
|
||||
}
|
||||
|
||||
/** 客服模式下返回客服专用 URL,否则返回商家原 URL */
|
||||
export function merchantApi(staffPath, ownerPath) {
|
||||
return isStaffMode() ? staffPath : ownerPath;
|
||||
}
|
||||
|
||||
export function staffCan(action) {
|
||||
if (!isStaffMode()) return true;
|
||||
const map = {
|
||||
dispatch: 'order_dispatch',
|
||||
detail: 'order_detail',
|
||||
settle: 'order_settle',
|
||||
cancel: 'order_cancel',
|
||||
refund: 'order_refund_apply',
|
||||
penalty: 'penalty_apply',
|
||||
penaltyManage: 'penalty_manage',
|
||||
changePlayer: 'order_change_player',
|
||||
staffManage: 'staff_manage',
|
||||
audit: 'audit_view',
|
||||
finance: 'finance_view',
|
||||
walletAllocate: 'wallet_allocate',
|
||||
walletRevoke: 'wallet_revoke',
|
||||
template: 'template_manage',
|
||||
imChat: 'im_chat',
|
||||
};
|
||||
return staffHasPerm(map[action] || action);
|
||||
}
|
||||
|
||||
/** 商家老板或有效子客服,均可进入商家端工作台 */
|
||||
export function isMerchantPortalUser() {
|
||||
return Number(wx.getStorageSync('shangjiastatus')) === 1 || isStaffMode();
|
||||
}
|
||||
|
||||
/** 将 /me 返回的 stats 写入页面 */
|
||||
export function applyStaffStatsToPage(page, ctx) {
|
||||
const st = (ctx && ctx.stats) || {};
|
||||
if (!st || typeof page.setData !== 'function') return;
|
||||
const patch = {};
|
||||
if (page.data.stats) {
|
||||
patch.stats = {
|
||||
...page.data.stats,
|
||||
todayDispatch: st.today_dispatch_count || 0,
|
||||
todayRefund: st.today_refund_count || 0,
|
||||
};
|
||||
}
|
||||
if ('jinridingdan' in page.data) {
|
||||
patch.jinridingdan = st.today_dispatch_count || 0;
|
||||
patch.jinrituikuan = st.today_refund_count || 0;
|
||||
patch.fabu = st.dispatch_count || 0;
|
||||
patch.tuikuan = st.refund_count || 0;
|
||||
}
|
||||
if (Object.keys(patch).length) {
|
||||
page.setData(patch);
|
||||
}
|
||||
}
|
||||
|
||||
/** 同步子客服 UI 权限到页面 data(并确保 isShangjia 对客服为 true) */
|
||||
export function syncStaffUi(page) {
|
||||
const staff = isStaffMode();
|
||||
const ctx = staff ? (getStaffContext() || {}) : null;
|
||||
const wallet = (ctx && ctx.wallet) || {};
|
||||
const portal = isMerchantPortalUser();
|
||||
page.setData({
|
||||
isShangjia: portal,
|
||||
isStaffMode: staff,
|
||||
isMerchantOwner: !staff && Number(wx.getStorageSync('shangjiastatus')) === 1,
|
||||
staffRoleName: staff ? (ctx.role_name || '客服') : '',
|
||||
staffMerchantId: staff ? (ctx.merchant_id || '') : '',
|
||||
quotaAvailable: staff ? (wallet.quota_available || '0.00') : '',
|
||||
canDispatch: staffCan('dispatch'),
|
||||
canSettle: staffCan('settle'),
|
||||
canCancel: staffCan('cancel'),
|
||||
canRefund: staffCan('refund'),
|
||||
canPenalty: staffCan('penalty'),
|
||||
canPenaltyManage: staffCan('penaltyManage'),
|
||||
canChangePlayer: staffCan('changePlayer'),
|
||||
canStaffManage: staffCan('staffManage'),
|
||||
canWalletAllocate: staffCan('walletAllocate'),
|
||||
canWalletRevoke: staffCan('walletRevoke'),
|
||||
canFinance: staffCan('finance'),
|
||||
canTemplate: staffCan('template'),
|
||||
canAudit: !staff || staffCan('audit'),
|
||||
canImChat: !staff || staffCan('imChat'),
|
||||
showRechargeWithdraw: isMerchantOwnerOnly(),
|
||||
});
|
||||
}
|
||||
|
||||
/** 客服模式走 staff 专用 URL,老板走原商家 URL */
|
||||
export function staffOrOwner(staffUrl, ownerUrl) {
|
||||
return isStaffMode() ? staffUrl : ownerUrl;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
// utils/upload.js
|
||||
import request from './request.js'
|
||||
import { buildClubHeaders } from './club-context.js'
|
||||
|
||||
const app = getApp()
|
||||
|
||||
@@ -39,9 +40,9 @@ const upload = async (options) => {
|
||||
filePath,
|
||||
name: fileName,
|
||||
formData,
|
||||
header: {
|
||||
header: buildClubHeaders({
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
}),
|
||||
success: (res) => {
|
||||
|
||||
if (res.statusCode === 200) {
|
||||
|
||||
Reference in New Issue
Block a user