添加个人中心及按角色展示内容

This commit is contained in:
2026-03-11 16:12:00 +08:00
parent 4df88ea502
commit 2056f69c3e
45 changed files with 1108 additions and 23 deletions

View File

@@ -63,8 +63,8 @@
<!-- 内容展示区 (内部路由渲染) -->
<view class="content-scroll">
<view class="content-inner" :class="{ 'is-mobile': isMobile }">
<slot></slot>
<component :is="currentComponent" v-if="!isPageLoading && currentComponent != null"></component>
<slot v-if="hasAccess"></slot>
<component :is="currentComponent" v-if="hasAccess && !isPageLoading && currentComponent != null"></component>
<AdminPageLoading v-if="isPageLoading"></AdminPageLoading>
</view>
<AdminFooter></AdminFooter>
@@ -116,6 +116,7 @@ import {
import type { TabItem } from '@/layouts/admin/store/adminNavStore.uts'
import { getComponent } from '@/layouts/admin/router/adminComponentMap.uts'
import { hasAdminModuleAccess } from '@/layouts/admin/utils/role.uts'
const props = defineProps({
currentPage: {
@@ -131,6 +132,12 @@ const SUB_W = 200
// 页面加载状态
const isPageLoading = ref(false)
const hasAccess = computed<boolean>(() => {
return hasAdminModuleAccess(activeTopMenuId.value)
})
const hasNotification = ref<boolean>(false)
/**
@@ -258,6 +265,8 @@ const breadcrumb = computed<Array<{id: string, title: string}>>(() => {
// 当前渲染的组件
const currentComponent = computed<any>(() => {
const route = findRouteById(activeRouteId.value)
if (!route) return null
return getComponent(route.componentKey)

View File

@@ -25,24 +25,74 @@
<view class="header-right">
<view class="hbtn" @click="$emit('search')"><text>🔍</text></view>
<view v-if="!isMobile" class="hbtn" @click="$emit('refresh')"><text>⟳</text></view>
<view class="hbtn" @click="$emit('notify')">
<view class="hbtn" @click="$emit('notify')">
<text>🔔</text>
<view class="dot" v-if="hasNotification"></view>
</view>
<!-- 用户个人中心 / 下拉菜单 -->
<view class="user-profile-container" @click="goToUserCenter">
<text class="user-name">crmeb demo</text>
<text class="user-arrow">▼</text>
<view class="user-dropdown" v-if="showUserMenu">
<view class="dropdown-item" @click.stop="goToUserCenter">个人中心</view>
<view class="dropdown-item" @click.stop="handleLogout">退出登录</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { computed } from 'vue'
import {
toggleSubSider,
showSubSider,
layoutMode,
import { ref, computed } from 'vue'
import {
toggleSubSider,
showSubSider,
layoutMode,
isOverlayVisible,
isMobileMenuOpen
isMobileMenuOpen,
openRoute
} from '@/layouts/admin/store/adminNavStore.uts'
const showUserMenu = ref(false)
function closeUserMenu() {
showUserMenu.value = false
}
function toggleUserMenu() {
showUserMenu.value = !showUserMenu.value
}
function goToUserCenter() {
uni.showToast({ title: "尝试打开个人中心...", icon: "none", duration: 2000 });
uni.showToast({ title: "尝试打开个人中心...", icon: "none", duration: 2000 });
console.log("[AdminHeader] goToUserCenter called");
console.log("[AdminHeader] goToUserCenter called");
showUserMenu.value = false
openRoute('home_user_center')
}
function handleLogout() {
showUserMenu.value = false
uni.showModal({
title: '提示',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
uni.removeStorageSync('adminRole')
uni.removeStorageSync('token')
uni.reLaunch({
url: '/pages/user/login'
})
}
}
})
}
const props = defineProps<{
breadcrumb: Array<{id: string, title: string}>
hasNotification: boolean
@@ -82,6 +132,10 @@ const currentTitle = computed((): string => {
<style>
.header{
position: relative;
z-index: 1001;
position: relative;
z-index: 1001;
height: 56px;
background:#fff;
border-bottom: 1px solid #eef2f7;
@@ -142,6 +196,12 @@ const currentTitle = computed((): string => {
display: flex !important;
}
.header-right {
position: relative;
z-index: 100;
pointer-events: auto;
position: relative;
z-index: 100;
pointer-events: auto;
gap: 5px;
}
}
@@ -151,7 +211,13 @@ const currentTitle = computed((): string => {
margin: 0 8px;
}
.header-right{
.header-right{
position: relative;
z-index: 100;
pointer-events: auto;
position: relative;
z-index: 100;
pointer-events: auto;
display:flex;
flex-direction:row;
align-items:center;
@@ -176,4 +242,64 @@ const currentTitle = computed((): string => {
top: 6px;
right: 6px;
}
.user-profile-container {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
margin-left: 10px;
cursor: pointer;
}
.user-name {
font-size: 14px;
color: #333;
}
.user-arrow {
font-size: 12px;
color: #666;
margin-left: 4px;
}
.user-dropdown {
z-index: 1002;
z-index: 1002;
position: absolute;
top: 40px;
right: 0;
width: 120px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px rgba(0,0,0,0.1);
z-index: 1000;
display: flex;
flex-direction: column;
}
.dropdown-item {
height: 40px;
line-height: 40px;
text-align: center;
font-size: 14px;
color: #333;
cursor: pointer;
}
.dropdown-item:hover {
background-color: #f5f7fa;
color: #1890ff;
}
.user-dropdown-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
background-color: transparent;
}
</style>

View File

@@ -16,6 +16,7 @@ import PlaceholderPage from '@/layouts/admin/components/PlaceholderPage.uvue'
// 导入首页(内部组件,不包含 AdminLayout
import HomeIndex from '@/layouts/admin/pages/HomeIndex.uvue'
import UserCenter from '@/pages/mall/admin/userCenter/index.uvue'
// --- 用户模块 ---
import UserStatistic from '@/pages/mall/admin/user/statistics/index.uvue'
@@ -179,6 +180,7 @@ import MaintainSysInfo from '@/pages/mall/admin/maintain/sys/info.uvue'
export const componentMap: Map<string, any> = new Map([
// 首页
['HomeIndex', HomeIndex],
['UserCenter', UserCenter],
// 用户模块
['UserStatistic', UserStatistic],

View File

@@ -29,6 +29,8 @@ export type RouteRecord = {
/**
* 菜单分组类型
*/
import { hasAdminModuleAccess } from '@/layouts/admin/utils/role.uts'
export type MenuGroup = {
id: string
title: string
@@ -232,8 +234,17 @@ export const routes: RouteRecord[] = [
},
// ========== 用户模块 ==========
{
id: 'user_statistic',
// ========== 个人中心 ==========
{
id: 'home_user_center',
title: '个人中心',
path: '/pages/mall/admin/userCenter/index',
componentKey: 'UserCenter',
order: 100
},
{
id: 'user_statistic',
title: '用户统计',
path: '/pages/mall/admin/user/statistics/index',
componentKey: 'UserStatistic',
@@ -1175,7 +1186,10 @@ export const routes: RouteRecord[] = [
* 获取所有一级菜单
*/
export function getTopMenus(): TopMenu[] {
return topMenus.sort((a, b) => a.order - b.order)
// 基于 role 的模块过滤
return topMenus
.filter(m => hasAdminModuleAccess(m.id))
.sort((a, b) => a.order - b.order)
}
/**

View File

@@ -12,6 +12,7 @@ import {
getTopMenus
} from '@/layouts/admin/router/adminRoutes.uts'
import { addView, activeFullPath, visitedViews } from './tagsViewStore.uts'
import { hasAdminModuleAccess } from '@/layouts/admin/utils/role.uts'
/**
* 标签页类型
@@ -77,14 +78,34 @@ export const isOverlayVisible = ref<boolean>(false)
* @param addTab 是否添加到标签页
*/
export function openRoute(routeId: string, addTab: boolean = true): void {
const route = findRouteById(routeId)
if (!route) {
console.warn(`[AdminNav] Route not found: ${routeId}`)
return
}
// 基于 role 的页面访问拦截
// route.parentId 对应上方 topMenus 的 id。这里校验是否有权限
const moduleId = route.parentId ? route.parentId : route.id.split('_')[0]
if (!hasAdminModuleAccess(moduleId)) {
uni.showToast({
title: '您没有权限访问该模块',
icon: 'none'
})
console.warn(`[AdminNav] Access denied for role to module: ${moduleId}`)
// 回退到首页
if (routeId !== 'home_index') {
openRoute('home_index', addTab)
}
return
}
// 更新当前路由
activeRouteId.value = routeId
// 更新一级菜单选中态
if (route.parentId) {

View File

@@ -0,0 +1,51 @@
// Admin role-based access control
export function getCurrentAdminRole(): string {
// 从本地存储获取当前 role。为不影响全局优先读 admin_role默认降级到读全局 role/userInfo再默认
// 根据实际系统中的 token/userInfo 结构可灵活调整
const roleType = uni.getStorageSync('admin_role') as string
if (roleType && typeof roleType === 'string' && roleType.length > 0) {
return roleType
}
// 检查是否有关联 merchant_id 或者是存了全局 role
const merchantId = uni.getStorageSync('merchant_id')
if (merchantId) return 'merchant'
const globalRole = uni.getStorageSync('role') as string
if (globalRole && typeof globalRole === 'string' && globalRole.length > 0) {
return globalRole
}
return 'admin' // 默认返回 admin 作为兜底(兼容原有全部显示逻辑)
}
// 获取不同 role 允许访问的顶级模块 ID (主侧边栏的顶层菜单 id)
export function getVisibleTopMenuIds(role: string): string[] {
if (role === 'admin') {
return ['home', 'user', 'order', 'product', 'marketing', 'distribution', 'kefu', 'finance', 'cms', 'decoration', 'app', 'setting', 'maintain']
}
if (role === 'merchant') {
// merchant: 只能看到 主页、订单、商品、营销、财务
return ['home', 'order', 'product', 'marketing', 'finance']
}
// 其他 role: 安全兜底
return ['home']
}
// 判断是否有权限访问某模块或路由
export function hasAdminModuleAccess(moduleId: string | undefined): boolean {
if (!moduleId) return true // 没有指定的通常认为是公共的,比如一些通用组件
const role = getCurrentAdminRole()
if (role === 'admin') return true
// 对于 merchant 角色,允许其访问的顶级 menu id 及其所属路由
if (role === 'merchant') {
const allowed = ['home', 'order', 'product', 'marketing', 'finance']
return allowed.includes(moduleId)
}
return false
}