backup: 紫色UI换肤前完整备份(当前橙色逍遥梦主题)
保留改造前全部页面样式与功能代码,便于回滚。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
getSessionToken,
|
||||
isWechatLoginSuccess,
|
||||
} from './role-tab-bar';
|
||||
import { restoreStaffContextAfterAuth } from './staff-api.js';
|
||||
|
||||
const app = getApp();
|
||||
|
||||
@@ -14,6 +15,7 @@ export function ensureLogin(options = {}) {
|
||||
const token = getSessionToken(app);
|
||||
if (token) {
|
||||
backfillUserProfileCache(app);
|
||||
restoreStaffContextAfterAuth();
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,57 +1,31 @@
|
||||
/**
|
||||
* 手机号强制认证 - 完全自包含逻辑模块
|
||||
*
|
||||
* 特点:
|
||||
* 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';
|
||||
|
||||
// ==================== 内部封装: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;
|
||||
}
|
||||
const token = wx.getStorageSync('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);
|
||||
}
|
||||
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 +34,6 @@ function innerUpload(url, filePath, formData = {}, fileName = 'avatar') {
|
||||
return;
|
||||
}
|
||||
|
||||
// 先验证文件是否存在
|
||||
wx.getFileInfo({
|
||||
filePath,
|
||||
success: () => {
|
||||
@@ -69,15 +42,11 @@ function innerUpload(url, filePath, formData = {}, fileName = 'avatar') {
|
||||
filePath,
|
||||
name: fileName,
|
||||
formData,
|
||||
header: {
|
||||
'Authorization': 'Bearer ' + token
|
||||
},
|
||||
header: { 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 +57,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,
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
145
utils/request.js
145
utils/request.js
@@ -1,74 +1,73 @@
|
||||
// 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,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// utils/request.js
|
||||
function request(options) {
|
||||
const app = getApp();
|
||||
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;
|
||||
@@ -1,255 +1,266 @@
|
||||
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.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);
|
||||
}
|
||||
|
||||
253
utils/staff-api.js
Normal file
253
utils/staff-api.js
Normal file
@@ -0,0 +1,253 @@
|
||||
/**
|
||||
* 商家子客服 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`,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user