/** * 全局双身份与角色守卫管理模块 (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; }