293 行
8.4 KiB
JavaScript
293 行
8.4 KiB
JavaScript
/**
|
||
* 全局双身份与角色守卫管理模块 (Role Guard Manager)
|
||
* 覆盖四大核心角色:
|
||
* 1. PLAYER: 普通买家/玩家
|
||
* 2. EXPERT: 陪玩大神/接单人
|
||
* 3. HOST: 车队房主/局长
|
||
* 4. ADMIN: 平台管理员/运营端
|
||
*/
|
||
|
||
export const ROLE_TYPES = {
|
||
PLAYER: 'PLAYER', // 发单人端(普通玩家/发起活动/找陪玩)
|
||
EXPERT: 'EXPERT', // 接单人端(大神接单/5km雷达定位/核销)
|
||
HOST: 'HOST', // 车队房主/局长(发起AA组局/动态调价/解散退款/到场核销)
|
||
ADMIN: 'ADMIN', // 平台管理员(资质审核/争议仲裁/大盘监管)
|
||
};
|
||
|
||
export const CERT_STATUS = {
|
||
NONE: 0, // 未提交
|
||
PENDING: 1, // 待审核
|
||
APPROVED: 2, // 审核通过
|
||
REJECTED: 3, // 驳回
|
||
};
|
||
|
||
const STORAGE_KEY_ROLE = 'PW_CURRENT_USER_ROLE';
|
||
const STORAGE_KEY_CERT = 'PW_EXPERT_CERT_STATUS';
|
||
|
||
export function getExpertCertStatus() {
|
||
try {
|
||
const saved = uni.getStorageSync(STORAGE_KEY_CERT);
|
||
if (saved !== '' && saved !== null && saved !== undefined) {
|
||
return Number(saved);
|
||
}
|
||
return CERT_STATUS.NONE;
|
||
} catch (e) {
|
||
return CERT_STATUS.NONE;
|
||
}
|
||
}
|
||
|
||
export function setExpertCertStatus(status) {
|
||
const target = Number(status);
|
||
try {
|
||
uni.setStorageSync(STORAGE_KEY_CERT, target);
|
||
} catch (e) {
|
||
console.error('Failed to persist cert status', e);
|
||
}
|
||
uni.$emit('cert-status-change', target);
|
||
if (target !== CERT_STATUS.APPROVED) {
|
||
setCurrentRole(ROLE_TYPES.PLAYER);
|
||
}
|
||
return target;
|
||
}
|
||
|
||
export function isExpertCertified() {
|
||
return getExpertCertStatus() === CERT_STATUS.APPROVED;
|
||
}
|
||
|
||
export function canSwitchToRole(targetRole) {
|
||
if (targetRole === ROLE_TYPES.EXPERT) {
|
||
if (!isExpertCertified()) {
|
||
return {
|
||
allowed: false,
|
||
reason: '尚未开通或通过大神实名认证,无法切换至接单模式',
|
||
};
|
||
}
|
||
}
|
||
return { allowed: true };
|
||
}
|
||
|
||
export function getCurrentRole() {
|
||
try {
|
||
const saved = uni.getStorageSync(STORAGE_KEY_ROLE);
|
||
if (saved === ROLE_TYPES.EXPERT) {
|
||
if (isExpertCertified()) {
|
||
return ROLE_TYPES.EXPERT;
|
||
}
|
||
return ROLE_TYPES.PLAYER;
|
||
}
|
||
return ROLE_TYPES.PLAYER;
|
||
} catch (e) {
|
||
return ROLE_TYPES.PLAYER;
|
||
}
|
||
}
|
||
|
||
export function setCurrentRole(role) {
|
||
let target = role === ROLE_TYPES.EXPERT ? ROLE_TYPES.EXPERT : ROLE_TYPES.PLAYER;
|
||
if (target === ROLE_TYPES.EXPERT && !isExpertCertified()) {
|
||
console.warn('[RoleGuard] 未认证大神禁止切换至接单端,已自动降级回玩家端');
|
||
target = ROLE_TYPES.PLAYER;
|
||
}
|
||
try {
|
||
uni.setStorageSync(STORAGE_KEY_ROLE, target);
|
||
} catch (e) {
|
||
console.error('Failed to persist user role', e);
|
||
}
|
||
uni.$emit('role-change', target);
|
||
return target;
|
||
}
|
||
|
||
export function toggleRole() {
|
||
const current = getCurrentRole();
|
||
if (current === ROLE_TYPES.PLAYER) {
|
||
if (!isExpertCertified()) {
|
||
return {
|
||
success: false,
|
||
currentRole: ROLE_TYPES.PLAYER,
|
||
message: '尚未通过大神实名认证,请先提交入驻认证',
|
||
};
|
||
}
|
||
setCurrentRole(ROLE_TYPES.EXPERT);
|
||
return {
|
||
success: true,
|
||
currentRole: ROLE_TYPES.EXPERT,
|
||
message: '已切换至大神接单模式',
|
||
};
|
||
} else {
|
||
setCurrentRole(ROLE_TYPES.PLAYER);
|
||
return {
|
||
success: true,
|
||
currentRole: ROLE_TYPES.PLAYER,
|
||
message: '已切回发单人端',
|
||
};
|
||
}
|
||
}
|
||
|
||
export function isUserLoggedIn() {
|
||
try {
|
||
if (typeof uni === 'undefined') return false;
|
||
const uid = uni.getStorageSync('userId');
|
||
const hasLoggedOut = uni.getStorageSync('user_has_logged_out');
|
||
if (hasLoggedOut === '1') return false;
|
||
return Boolean(uid && String(uid).trim() !== '' && String(uid) !== '0');
|
||
} catch (e) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
export function isUserBanned() {
|
||
try {
|
||
if (typeof uni === 'undefined') return false;
|
||
const uInfo = uni.getStorageSync('userInfo') || uni.getStorageSync('PW_USER_INFO') || uni.getStorageSync('USER_INFO');
|
||
return Boolean(uInfo && (uInfo.status === '已封禁' || uInfo.status === '封禁'));
|
||
} catch (e) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 必须登录才能访问的受保护业务页面清单 (未登录游客访问自动拦截并跳登录)
|
||
export const AUTH_GUARD_PAGES = [
|
||
'/pages/activity/create', // 发单/发起组队
|
||
'/pages/order/create', // 确认下单/预约
|
||
'/pages/order/list', // 我的订单中心
|
||
'/pages/activity/my-ticket', // 我的活动门票 (4位核销码)
|
||
'/pages/user/recharge', // 钱包充值
|
||
'/pages/user/withdraw', // 收益提现
|
||
'/pages/user/withdraw-records', // 提现审批明细
|
||
'/pages/user/bill-records', // 账单明细
|
||
'/pages/user/promote', // 推广赚钱
|
||
'/pages/user/favorites', // 我的收藏
|
||
'/pages/user/reports', // 我的举报与维权
|
||
'/pages/user/settings', // 系统与隐私设置
|
||
'/pages/expert/apply', // 大神入驻认证
|
||
'/pages/expert/workspace', // 大神接单工作台
|
||
'/pages/expert/radar', // 我要接单 (附近5km雷达)
|
||
'/pages/expert/hall' // 接单大厅
|
||
];
|
||
|
||
export function ensureLoginOrRedirect(redirectUrl = '') {
|
||
if (!isUserLoggedIn()) {
|
||
uni.showToast({
|
||
title: '请先登录账号',
|
||
icon: 'none',
|
||
duration: 1500
|
||
});
|
||
setTimeout(() => {
|
||
const target = redirectUrl ? `/pages/auth/login?redirect=${encodeURIComponent(redirectUrl)}` : '/pages/auth/login';
|
||
uni.navigateTo({
|
||
url: target,
|
||
fail: () => {
|
||
uni.switchTab({ url: '/pages/user/index' });
|
||
}
|
||
});
|
||
}, 350);
|
||
return false;
|
||
}
|
||
|
||
if (isUserBanned()) {
|
||
uni.showToast({
|
||
title: '账号已被封禁',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
// 非大神禁止访问的受保护页面清单
|
||
export const EXPERT_GUARD_PAGES = [
|
||
'/pages/expert/workspace',
|
||
'/pages/expert/radar',
|
||
'/pages/expert/hall',
|
||
];
|
||
|
||
export function checkRoleGuard(pageUrl) {
|
||
if (!pageUrl) return { allowed: true };
|
||
const pureUrl = pageUrl.split('?')[0];
|
||
|
||
// 1. 登录守卫拦截 (针对未登录游客)
|
||
if (AUTH_GUARD_PAGES.includes(pureUrl)) {
|
||
if (!isUserLoggedIn()) {
|
||
return {
|
||
allowed: false,
|
||
code: 'UNAUTHORIZED',
|
||
redirectUrl: `/pages/auth/login?redirect=${encodeURIComponent(pageUrl)}`,
|
||
message: '该功能需要登录账号,正在为您跳转登录...',
|
||
};
|
||
}
|
||
}
|
||
|
||
// 2. 大神身份守卫拦截 (针对已登录但非认证大神的普通玩家)
|
||
if (EXPERT_GUARD_PAGES.includes(pureUrl)) {
|
||
if (!isExpertCertified()) {
|
||
return {
|
||
allowed: false,
|
||
code: 'FORBIDDEN_NOT_EXPERT',
|
||
redirectUrl: '/pages/user/index',
|
||
message: '非大神禁止访问接单工作台与雷达,请先在个人中心完成实名认证',
|
||
};
|
||
}
|
||
}
|
||
return { allowed: true };
|
||
}
|
||
|
||
let guardInstalled = false;
|
||
export function installRoleGuard() {
|
||
if (guardInstalled) return;
|
||
guardInstalled = true;
|
||
const routeMethods = ['navigateTo', 'redirectTo', 'reLaunch', 'switchTab'];
|
||
routeMethods.forEach((method) => {
|
||
try {
|
||
if (typeof uni !== 'undefined' && typeof uni.addInterceptor === 'function') {
|
||
uni.addInterceptor(method, {
|
||
invoke(args) {
|
||
if (!args || !args.url) return true;
|
||
const check = checkRoleGuard(args.url);
|
||
if (!check.allowed) {
|
||
uni.showToast({
|
||
title: check.message || '请先登录账号',
|
||
icon: 'none',
|
||
duration: 2000,
|
||
});
|
||
if (check.redirectUrl) {
|
||
setTimeout(() => {
|
||
uni.navigateTo({
|
||
url: check.redirectUrl,
|
||
fail: () => {
|
||
uni.switchTab({ url: '/pages/user/index' });
|
||
}
|
||
});
|
||
}, 350);
|
||
}
|
||
return false;
|
||
}
|
||
return true;
|
||
},
|
||
});
|
||
}
|
||
} catch (e) {}
|
||
});
|
||
}
|
||
|
||
let isRedirectingToExpert = false;
|
||
export function safeRedirectToExpertHall(target = '/pages/expert/hall') {
|
||
if (isRedirectingToExpert) {
|
||
return true;
|
||
}
|
||
if (getCurrentRole() === ROLE_TYPES.EXPERT && isExpertCertified()) {
|
||
isRedirectingToExpert = true;
|
||
uni.reLaunch({
|
||
url: target || '/pages/expert/hall',
|
||
complete: () => {
|
||
setTimeout(() => {
|
||
isRedirectingToExpert = false;
|
||
}, 500);
|
||
}
|
||
});
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|