新增 用户身份的页面权限控制系统
This commit is contained in:
parent
d59584fc28
commit
32730f9299
141
src/components/PermissionGuard.vue
Normal file
141
src/components/PermissionGuard.vue
Normal file
@ -0,0 +1,141 @@
|
||||
<!-- 权限控制组件示例 -->
|
||||
<template>
|
||||
<div class="permission-guard">
|
||||
<!-- 权限不足提示 -->
|
||||
<div v-if="!hasPermission && showDeniedMessage" class="permission-denied">
|
||||
<div class="denied-icon">🚫</div>
|
||||
<h3>Access Denied</h3>
|
||||
<p>You don't have permission to access this feature.</p>
|
||||
<p>Current role: {{ roleDisplayName }}</p>
|
||||
<button @click="redirectToDefaultPage" class="redirect-btn">
|
||||
Go to {{ defaultPageName }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 有权限时显示内容 -->
|
||||
<slot v-else></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { permissionManager, hasPermission, getDefaultPage, getPrimaryRole } from '../utils/permissionManager.js'
|
||||
|
||||
const props = defineProps({
|
||||
// 需要的权限页面路径
|
||||
requiredPage: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
// 是否显示权限不足消息
|
||||
showDeniedMessage: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 权限不足时是否自动重定向
|
||||
autoRedirect: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
// 计算权限
|
||||
const hasPagePermission = computed(() => {
|
||||
return hasPermission(props.requiredPage)
|
||||
})
|
||||
|
||||
// 实际显示权限(考虑是否已加载)
|
||||
const hasPermission = computed(() => {
|
||||
return permissionManager.isLoaded() && hasPagePermission.value
|
||||
})
|
||||
|
||||
// 当前角色显示名称
|
||||
const roleDisplayName = computed(() => {
|
||||
return permissionManager.getRoleDisplayName()
|
||||
})
|
||||
|
||||
// 默认页面名称
|
||||
const defaultPageName = computed(() => {
|
||||
const defaultPage = getDefaultPage()
|
||||
const pageNames = {
|
||||
'/coin-seller': 'Coin Seller',
|
||||
'/agency-center': 'Agency Center',
|
||||
'/bd-center': 'BD Center',
|
||||
'/host-center': 'Host Center',
|
||||
'/apply': 'Application'
|
||||
}
|
||||
return pageNames[defaultPage] || 'Home'
|
||||
})
|
||||
|
||||
// 重定向到默认页面
|
||||
const redirectToDefaultPage = () => {
|
||||
const defaultPage = getDefaultPage()
|
||||
router.push(defaultPage)
|
||||
}
|
||||
|
||||
// 组件挂载时检查自动重定向
|
||||
onMounted(() => {
|
||||
if (props.autoRedirect && !hasPermission.value && permissionManager.isLoaded()) {
|
||||
redirectToDefaultPage()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.permission-guard {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.permission-denied {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.denied-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.permission-denied h3 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.permission-denied p {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 16px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.redirect-btn {
|
||||
margin-top: 20px;
|
||||
padding: 12px 24px;
|
||||
background-color: #8b5cf6;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.redirect-btn:hover {
|
||||
background-color: #7c3aed;
|
||||
}
|
||||
|
||||
.redirect-btn:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
</style>
|
||||
@ -18,6 +18,9 @@ if (versionChecker.checkVersion()) {
|
||||
// }
|
||||
}
|
||||
|
||||
// 初始化权限系统
|
||||
console.debug('🔐 Permission system initialized')
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(router)
|
||||
|
||||
@ -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
|
||||
|
||||
220
src/utils/permissionManager.js
Normal file
220
src/utils/permissionManager.js
Normal file
@ -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()
|
||||
294
src/utils/routeGuard.js
Normal file
294
src/utils/routeGuard.js
Normal file
@ -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<string>} 返回主要身份
|
||||
*/
|
||||
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<string>} 主要身份
|
||||
*/
|
||||
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<boolean>} 是否进行了重定向
|
||||
*/
|
||||
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()
|
||||
@ -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,显示占位符
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user