From 32730f929971567ec10b98dfea212f9347d019a4 Mon Sep 17 00:00:00 2001 From: tianfeng <769204422@qq.com> Date: Fri, 22 Aug 2025 11:57:19 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E8=BA=AB=E4=BB=BD=E7=9A=84=E9=A1=B5=E9=9D=A2=E6=9D=83=E9=99=90?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/PermissionGuard.vue | 141 ++++++++++++++ src/main.js | 3 + src/router/index.js | 115 ++++++----- src/utils/permissionManager.js | 220 +++++++++++++++++++++ src/utils/routeGuard.js | 294 +++++++++++++++++++++++++++++ src/views/HostCenterView.vue | 68 +------ 6 files changed, 739 insertions(+), 102 deletions(-) create mode 100644 src/components/PermissionGuard.vue create mode 100644 src/utils/permissionManager.js create mode 100644 src/utils/routeGuard.js diff --git a/src/components/PermissionGuard.vue b/src/components/PermissionGuard.vue new file mode 100644 index 0000000..06ebc6f --- /dev/null +++ b/src/components/PermissionGuard.vue @@ -0,0 +1,141 @@ + + + + + + 🚫 + Access Denied + You don't have permission to access this feature. + Current role: {{ roleDisplayName }} + + Go to {{ defaultPageName }} + + + + + + + + + + + diff --git a/src/main.js b/src/main.js index 2033ea7..657c0c7 100644 --- a/src/main.js +++ b/src/main.js @@ -18,6 +18,9 @@ if (versionChecker.checkVersion()) { // } } +// 初始化权限系统 +console.debug('🔐 Permission system initialized') + const app = createApp(App) app.use(router) diff --git a/src/router/index.js b/src/router/index.js index ab53484..6709463 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,115 +1,144 @@ import { createRouter, createWebHistory } from 'vue-router' - +import { installRouteGuard } from '../utils/routeGuard.js' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: '/', - name: 'home', - component: () => import('../views/HostCenterView.vue'), + redirect: '/host-center' // 默认重定向,实际会被路由守卫处理 }, + { + path: '/host-center', + name: 'host-center', + component: () => import('../views/HostCenterView.vue'), + meta: { requiresAuth: true } + }, + { + path: '/agency-center', + name: 'agency-center', + component: () => import('../views/AgencyCenterView.vue'), + meta: { requiresAuth: true } + }, + { + path: '/bd-center', + name: 'bd-center', + component: () => import('../views/BDCenterView.vue'), + meta: { requiresAuth: true } + }, + { + path: '/coin-seller', + name: 'coin-seller', + component: () => import('../views/CoinSellerView.vue'), + meta: { requiresAuth: true } + }, + { + path: '/apply', + name: 'apply', + component: () => import('../views/ApplyView.vue'), + meta: { requiresAuth: false } + }, + { + path: '/message', + name: 'message', + component: () => import('../views/MessageView.vue'), + meta: { requiresAuth: true } + }, + { + path: '/transfer', + name: 'transfer', + component: () => import('../views/TransferView.vue'), + meta: { requiresAuth: true } + }, + { + path: '/exchange-gold-coins', + name: 'exchange-gold-coins', + component: () => import('../views/ExchangeGoldCoinsView.vue'), + meta: { requiresAuth: true } + }, + // 辅助页面 { path: '/about', name: 'about', component: () => import('../views/AboutView.vue'), }, - { - path: '/apply', - name: 'apply', - component: () => import('../views/ApplyView.vue'), - }, - { - path: '/host-center', - redirect: '/' - }, { path: '/host-setting', name: 'host-setting', component: () => import('../views/HostSettingView.vue'), - }, - - { - path: '/transfer', - name: 'transfer', - component: () => import('../views/TransferView.vue'), + meta: { requiresAuth: true } }, { path: '/search-payee', name: 'search-payee', component: () => import('../views/SearchPayeeView.vue'), + meta: { requiresAuth: true } }, { path: '/information-details', name: 'information-details', component: () => import('../views/InformationDetailsView.vue'), - }, - { - path: '/message', - name: 'message', - component: () => import('../views/MessageView.vue'), + meta: { requiresAuth: true } }, { path: '/invite-members', name: 'invite-members', component: () => import('../views/InviteMembersView.vue'), + meta: { requiresAuth: true } }, { path: '/team-bill', name: 'team-bill', component: () => import('../views/TeamBillView.vue'), + meta: { requiresAuth: true } }, { path: '/team-member', name: 'team-member', component: () => import('../views/TeamMemberView.vue'), + meta: { requiresAuth: true } }, { path: '/platform-policy', name: 'platform-policy', component: () => import('../views/PlatformPolicyView.vue'), - }, - { - path: '/coin-seller', - name: 'coin-seller', - component: () => import('../views/CoinSellerView.vue'), + meta: { requiresAuth: true } }, { path: '/coin-seller-search', name: 'coin-seller-search', component: () => import('../views/CoinSellerSearchView.vue'), + meta: { requiresAuth: true } }, { path: '/seller-records', name: 'seller-records', component: () => import('../views/SellerRecordsView.vue'), - }, - { - path: '/exchange-gold-coins', - name: 'exchange-gold-coins', - component: () => import('../views/ExchangeGoldCoinsView.vue'), - }, - { - path: '/agency-center', - name: 'agency-center', - component: () => import('../views/AgencyCenterView.vue'), - }, - { - path: '/bd-center', - name: 'bd-center', - component: () => import('../views/BDCenterView.vue'), + meta: { requiresAuth: true } }, { path: '/bd-setting', name: 'bd-setting', component: () => import('../views/BDSettingView.vue'), + meta: { requiresAuth: true } }, + // 公共页面 { path: '/not_app', name: 'not-app', component: () => import('../views/NotAppView.vue'), + meta: { requiresAuth: false } }, + // 404 页面 + { + path: '/:pathMatch(.*)*', + name: 'not-found', + redirect: '/apply' + } ], }) +// 安装路由守卫 +installRouteGuard(router) + export default router diff --git a/src/utils/permissionManager.js b/src/utils/permissionManager.js new file mode 100644 index 0000000..213be28 --- /dev/null +++ b/src/utils/permissionManager.js @@ -0,0 +1,220 @@ +/** + * 权限管理中心 + * 负责用户身份检查、权限验证、页面路由等核心功能 + */ + +// 用户身份类型 +export const USER_ROLES = { + FREIGHT_AGENT: 'freightAgent', + AGENT: 'agent', + BD: 'bd', + ANCHOR: 'anchor', + GUEST: 'guest' +} + +// 页面路径常量 +export const PAGES = { + COIN_SELLER: '/coin-seller', + AGENCY_CENTER: '/agency-center', + BD_CENTER: '/bd-center', + HOST_CENTER: '/host-center', + APPLY: '/apply', + MESSAGE: '/message', + TRANSFER: '/transfer', + EXCHANGE: '/exchange-gold-coins', + NOT_APP: '/not_app' +} + +// 页面权限映射表(根据你的需求) +export const PAGE_PERMISSIONS = { + [PAGES.COIN_SELLER]: [USER_ROLES.FREIGHT_AGENT], + [PAGES.AGENCY_CENTER]: [USER_ROLES.AGENT], + [PAGES.BD_CENTER]: [USER_ROLES.BD], + [PAGES.HOST_CENTER]: [USER_ROLES.AGENT, USER_ROLES.ANCHOR], + [PAGES.APPLY]: [USER_ROLES.GUEST], + [PAGES.MESSAGE]: [USER_ROLES.AGENT], + [PAGES.TRANSFER]: [USER_ROLES.AGENT, USER_ROLES.BD, USER_ROLES.ANCHOR], + [PAGES.EXCHANGE]: [USER_ROLES.AGENT, USER_ROLES.BD, USER_ROLES.ANCHOR], + [PAGES.NOT_APP]: [] // 所有人都可以访问 +} + +// 身份优先级(数字越大优先级越高) +export const ROLE_PRIORITY = { + [USER_ROLES.FREIGHT_AGENT]: 4, + [USER_ROLES.AGENT]: 3, + [USER_ROLES.BD]: 2, + [USER_ROLES.ANCHOR]: 1, + [USER_ROLES.GUEST]: 0 +} + +// 身份对应的默认首页 +export const ROLE_DEFAULT_PAGES = { + [USER_ROLES.FREIGHT_AGENT]: PAGES.COIN_SELLER, + [USER_ROLES.AGENT]: PAGES.AGENCY_CENTER, + [USER_ROLES.BD]: PAGES.BD_CENTER, + [USER_ROLES.ANCHOR]: PAGES.HOST_CENTER, + [USER_ROLES.GUEST]: PAGES.APPLY +} + +/** + * 权限管理类 + */ +class PermissionManager { + constructor() { + this.userIdentity = null + this.primaryRole = null + this.isIdentityLoaded = false + this.allowedPages = [] + } + + /** + * 设置用户身份信息 + * @param {Object} identity - 身份信息对象 + */ + setUserIdentity(identity) { + this.userIdentity = identity + this.primaryRole = this.calculatePrimaryRole(identity) + this.allowedPages = this.calculateAllowedPages(this.primaryRole) + this.isIdentityLoaded = true + + console.debug('🔐 User identity updated:', { + identity: this.userIdentity, + primaryRole: this.primaryRole, + allowedPages: this.allowedPages + }) + } + + /** + * 计算用户主要身份(优先级最高的身份) + * @param {Object} identity - 身份信息 + * @returns {string} 主要身份 + */ + calculatePrimaryRole(identity) { + if (!identity) return USER_ROLES.GUEST + + // 按优先级检查身份 + if (identity.freightAgent) return USER_ROLES.FREIGHT_AGENT + if (identity.agent) return USER_ROLES.AGENT + if (identity.bd) return USER_ROLES.BD + if (identity.anchor) return USER_ROLES.ANCHOR + + return USER_ROLES.GUEST + } + + /** + * 计算用户允许访问的页面 + * @param {string} primaryRole - 主要身份 + * @returns {Array} 允许访问的页面列表 + */ + calculateAllowedPages(primaryRole) { + const allowedPages = [] + + Object.entries(PAGE_PERMISSIONS).forEach(([page, roles]) => { + if (roles.length === 0 || roles.includes(primaryRole)) { + allowedPages.push(page) + } + }) + + return allowedPages + } + + /** + * 检查页面访问权限 + * @param {string} page - 页面路径 + * @returns {boolean} 是否有权限访问 + */ + hasPagePermission(page) { + if (!this.isIdentityLoaded) { + console.warn('⚠️ Identity not loaded yet') + return false + } + + // 检查是否在允许列表中 + const hasPermission = this.allowedPages.includes(page) + + console.debug('🔍 Permission check:', { + page, + primaryRole: this.primaryRole, + hasPermission + }) + + return hasPermission + } + + /** + * 获取用户默认首页 + * @returns {string} 默认页面路径 + */ + getDefaultPage() { + return ROLE_DEFAULT_PAGES[this.primaryRole] || PAGES.APPLY + } + + /** + * 获取当前用户身份 + * @returns {string} 当前身份 + */ + getPrimaryRole() { + return this.primaryRole + } + + /** + * 获取身份显示名称 + * @param {string} role - 身份标识 + * @returns {string} 显示名称 + */ + getRoleDisplayName(role = this.primaryRole) { + const roleNames = { + [USER_ROLES.FREIGHT_AGENT]: 'Freight Agent', + [USER_ROLES.AGENT]: 'Agent', + [USER_ROLES.BD]: 'BD', + [USER_ROLES.ANCHOR]: 'Anchor', + [USER_ROLES.GUEST]: 'Guest' + } + return roleNames[role] || 'Unknown' + } + + /** + * 检查是否有多重身份 + * @returns {Array} 拥有的所有身份 + */ + getAllRoles() { + if (!this.userIdentity) return [USER_ROLES.GUEST] + + const roles = [] + if (this.userIdentity.freightAgent) roles.push(USER_ROLES.FREIGHT_AGENT) + if (this.userIdentity.agent) roles.push(USER_ROLES.AGENT) + if (this.userIdentity.bd) roles.push(USER_ROLES.BD) + if (this.userIdentity.anchor) roles.push(USER_ROLES.ANCHOR) + + return roles.length > 0 ? roles : [USER_ROLES.GUEST] + } + + /** + * 重置权限信息 + */ + reset() { + this.userIdentity = null + this.primaryRole = null + this.isIdentityLoaded = false + this.allowedPages = [] + console.debug('🔄 Permission manager reset') + } + + /** + * 检查是否身份加载完成 + * @returns {boolean} + */ + isLoaded() { + return this.isIdentityLoaded + } +} + +// 创建全局权限管理实例 +export const permissionManager = new PermissionManager() + +// 便捷方法 +export const hasPermission = (page) => permissionManager.hasPagePermission(page) +export const getDefaultPage = () => permissionManager.getDefaultPage() +export const getPrimaryRole = () => permissionManager.getPrimaryRole() +export const setUserIdentity = (identity) => permissionManager.setUserIdentity(identity) +export const resetPermissions = () => permissionManager.reset() diff --git a/src/utils/routeGuard.js b/src/utils/routeGuard.js new file mode 100644 index 0000000..0c0a82a --- /dev/null +++ b/src/utils/routeGuard.js @@ -0,0 +1,294 @@ +/** + * 路由守卫 + * 提供页面访问权限控制和自动重定向功能 + */ + +import { permissionManager, PAGES, USER_ROLES } from './permissionManager.js' +import { getUserIdentity } from '../api/wallet.js' +import { getUserId } from './userStore.js' + +/** + * 身份检查器 + */ +class IdentityChecker { + constructor() { + this.isChecking = false + this.checkPromise = null + } + + /** + * 检查并设置用户身份 + * @param {string} userId - 用户ID + * @returns {Promise} 返回主要身份 + */ + async checkUserIdentity(userId) { + if (!userId) { + console.warn('⚠️ No user ID provided') + permissionManager.setUserIdentity(null) + return USER_ROLES.GUEST + } + + // 如果正在检查中,等待现有的检查完成 + if (this.isChecking && this.checkPromise) { + console.debug('⏳ Identity check already in progress, waiting...') + return await this.checkPromise + } + + this.isChecking = true + this.checkPromise = this._performIdentityCheck(userId) + + try { + const primaryRole = await this.checkPromise + return primaryRole + } finally { + this.isChecking = false + this.checkPromise = null + } + } + + /** + * 执行实际的身份检查 + * @param {string} userId - 用户ID + * @returns {Promise} 主要身份 + */ + async _performIdentityCheck(userId) { + try { + console.debug('🔍 Checking user identity for:', userId) + + const response = await getUserIdentity(userId) + + if (response && response.status && response.body) { + const identity = response.body + console.debug('✅ Identity fetched:', identity) + + // 设置权限信息 + permissionManager.setUserIdentity(identity) + + return permissionManager.getPrimaryRole() + } else { + console.warn('⚠️ Invalid identity response') + permissionManager.setUserIdentity(null) + return USER_ROLES.GUEST + } + } catch (error) { + console.error('❌ Failed to fetch user identity:', error) + permissionManager.setUserIdentity(null) + return USER_ROLES.GUEST + } + } + + /** + * 重置检查状态 + */ + reset() { + this.isChecking = false + this.checkPromise = null + } +} + +/** + * 页面重定向器 + */ +class PageRedirector { + /** + * 根据身份自动重定向到正确页面 + * @param {Object} router - Vue Router 实例 + * @param {string} targetPath - 目标页面路径 + * @param {string} currentPath - 当前页面路径 + * @returns {Promise} 是否进行了重定向 + */ + async redirectToCorrectPage(router, targetPath, currentPath) { + const primaryRole = permissionManager.getPrimaryRole() + + // 检查目标页面权限 + const hasPermission = permissionManager.hasPagePermission(targetPath) + + console.debug('🔄 Redirect check:', { + targetPath, + currentPath, + primaryRole, + hasPermission + }) + + if (hasPermission) { + // 有权限访问目标页面 + if (targetPath !== currentPath) { + console.debug('✅ Access granted, navigating to:', targetPath) + return false // 不需要重定向,让路由正常进行 + } + return false + } else { + // 无权限访问,重定向到默认页面 + const defaultPage = permissionManager.getDefaultPage() + + console.debug('❌ Access denied, redirecting to:', defaultPage) + + if (defaultPage !== currentPath) { + await router.replace(defaultPage) + return true // 已进行重定向 + } + return false + } + } + + /** + * 处理越权访问 + * @param {Object} router - Vue Router 实例 + * @param {string} attemptedPage - 尝试访问的页面 + */ + async handleUnauthorizedAccess(router, attemptedPage) { + const primaryRole = permissionManager.getPrimaryRole() + const defaultPage = permissionManager.getDefaultPage() + + console.warn('🚫 Unauthorized access attempt:', { + attemptedPage, + primaryRole, + redirectingTo: defaultPage + }) + + // 重定向到用户有权限的默认页面 + await router.replace(defaultPage) + } +} + +/** + * 路由守卫类 + */ +class RouteGuard { + constructor() { + this.identityChecker = new IdentityChecker() + this.pageRedirector = new PageRedirector() + this.isGuardActive = false + } + + /** + * 安装路由守卫 + * @param {Object} router - Vue Router 实例 + */ + install(router) { + router.beforeEach(async (to, from, next) => { + await this.handleRouteChange(to, from, next) + }) + + this.isGuardActive = true + console.debug('🛡️ Route guard installed') + } + + /** + * 处理路由变化 + * @param {Object} to - 目标路由 + * @param {Object} from - 来源路由 + * @param {Function} next - 路由继续函数 + */ + async handleRouteChange(to, from, next) { + console.group('🛡️ Route Guard Check') + console.debug('📍 Route change:', from.path, '→', to.path) + + try { + // 1. 检查是否需要身份验证的页面 + if (this.isPublicPage(to.path)) { + console.debug('🌐 Public page, allowing access') + console.groupEnd() + next() + return + } + + // 2. 检查用户身份是否已加载 + if (!permissionManager.isLoaded()) { + console.debug('🔍 Identity not loaded, checking...') + + const userId = getUserId() + if (!userId) { + console.debug('❌ No user ID, redirecting to apply') + console.groupEnd() + next(PAGES.APPLY) + return + } + + // 检查用户身份 + await this.identityChecker.checkUserIdentity(userId) + } + + // 3. 检查页面访问权限 + const hasPermission = permissionManager.hasPagePermission(to.path) + + if (hasPermission) { + console.debug('✅ Permission granted') + console.groupEnd() + next() + } else { + console.debug('❌ Permission denied') + const defaultPage = permissionManager.getDefaultPage() + console.debug('🔄 Redirecting to:', defaultPage) + console.groupEnd() + next(defaultPage) + } + + } catch (error) { + console.error('❌ Route guard error:', error) + console.groupEnd() + next(PAGES.APPLY) // 出错时重定向到申请页面 + } + } + + /** + * 检查是否为公共页面(无需权限验证) + * @param {string} path - 页面路径 + * @returns {boolean} + */ + isPublicPage(path) { + const publicPages = [ + PAGES.NOT_APP, + '/404', + '/error' + ] + return publicPages.includes(path) + } + + /** + * 手动触发身份检查和重定向 + * @param {Object} router - Vue Router 实例 + * @param {string} userId - 用户ID + */ + async checkAndRedirect(router, userId) { + try { + console.debug('🔄 Manual identity check and redirect') + + // 检查身份 + const primaryRole = await this.identityChecker.checkUserIdentity(userId) + + // 获取当前页面 + const currentPath = router.currentRoute.value.path + + // 检查当前页面权限 + const hasPermission = permissionManager.hasPagePermission(currentPath) + + if (!hasPermission) { + // 重定向到默认页面 + await this.pageRedirector.redirectToCorrectPage(router, currentPath, currentPath) + } + + return primaryRole + } catch (error) { + console.error('❌ Manual check and redirect failed:', error) + throw error + } + } + + /** + * 重置守卫状态 + */ + reset() { + this.identityChecker.reset() + permissionManager.reset() + console.debug('🔄 Route guard reset') + } +} + +// 创建全局路由守卫实例 +export const routeGuard = new RouteGuard() + +// 便捷方法 +export const installRouteGuard = (router) => routeGuard.install(router) +export const checkAndRedirect = (router, userId) => routeGuard.checkAndRedirect(router, userId) +export const resetRouteGuard = () => routeGuard.reset() diff --git a/src/views/HostCenterView.vue b/src/views/HostCenterView.vue index a6630b3..3f13642 100644 --- a/src/views/HostCenterView.vue +++ b/src/views/HostCenterView.vue @@ -128,21 +128,20 @@ import { ref, reactive, onMounted } from 'vue' import { useRouter } from 'vue-router' import MobileHeader from '../components/MobileHeader.vue' -import { getBankBalance, getUserIdentity, getTeamEntry, getTeamMemberWorkStatistics } from '../api/wallet.js' +import { getBankBalance, getTeamEntry, getTeamMemberWorkStatistics } from '../api/wallet.js' import { connectApplication, parseAccessOrigin, parseHeader, setHttpHeaders, isInApp } from '../utils/appBridge.js' import { appConnectionManager, isAppConnected, getAppHeaderInfo } from '../utils/appConnectionManager.js' +import { checkAndRedirect } from '../utils/routeGuard.js' import {getUserId, setUserInfo} from '../utils/userStore.js' import {showError} from "@/utils/toast.js"; const router = useRouter() // 当前身份 -const currentIdentity = ref('host') const loading = ref(false) const appConnected = ref(false) const headerInfo = ref({}) const userProfile = ref(null) -const identityChecked = ref(false) // 连接APP并获取认证信息(优化版) const connectToApp = async () => { @@ -177,6 +176,9 @@ const connectToApp = async () => { if (teamEntryResult != null) { await fetchBankBalance() await fetchWorkStatistics() + + // 使用路由守卫进行身份检查和重定向 + await checkAndRedirect(router, teamEntryResult) } console.groupEnd() return @@ -224,6 +226,9 @@ const connectToApp = async () => { if (teamEntryResult != null) { await fetchBankBalance() await fetchWorkStatistics() + + // 使用路由守卫进行身份检查和重定向 + await checkAndRedirect(router, teamEntryResult) } resolve() @@ -354,13 +359,7 @@ const fetchTeamEntry = async () => { userInfo.userAvatar = response.body.memberProfile.userAvatar } - // 获取用户ID后检查身份 - const userId = response.body.memberProfile?.id - if (userId) { - await checkUserIdentity(userId) - } - - return userId + return response.body.memberProfile?.id } } catch (error) { console.error('获取团队信息失败:', error) @@ -380,55 +379,6 @@ const fetchTeamEntry = async () => { return null } -// 获取用户身份并自动跳转 -const checkUserIdentity = async (userId) => { - if (!userId) { - console.error('用户ID不存在') - return - } - - // 防止重复检查 - if (identityChecked.value) { - console.debug('身份已检查,跳过') - return - } - - try { - identityChecked.value = true - const response = await getUserIdentity(userId) - - if (response && response.status && response.body) { - const { freightAgent, anchor, agent, bd } = response.body - - console.debug('用户身份权限:', { freightAgent, anchor, agent, bd }) - - // 根据优先级判断:freightAgent > agent > anchor > bd - if (freightAgent) { - console.debug('跳转到 coin-seller') - router.replace('/coin-seller') - } else if (agent) { - console.debug('跳转到 agency-center') - router.replace('/agency-center') - } else if (bd) { - console.debug('跳转到 bd-center') - router.replace('/bd-center') - } else if (anchor) { - // 保持在当前Host页面 - console.debug('保持在 host-center') - currentIdentity.value = 'host' - } else { - // 如果没有任何权限,默认保持在Host页面 - console.debug('无特殊权限,保持在 host-center') - currentIdentity.value = 'host' - } - } - } catch (error) { - console.error('获取用户身份失败:', error) - // 出错时保持在Host页面 - currentIdentity.value = 'host' - } -} - // 处理头像图片加载失败 const handleImageError = (event) => { // 图片加载失败时,清空userAvatar,显示占位符
You don't have permission to access this feature.
Current role: {{ roleDisplayName }}