From 149cc5f6b0d471f6f48733acce4702e5806020fc Mon Sep 17 00:00:00 2001 From: yuliang_guo Date: Tue, 3 Feb 2026 15:41:56 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=9D=83=E9=99=90=E5=92=8C=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 侧边栏:根据角色过滤菜单,无可访问子菜单时隐藏父菜单 2. Dashboard:智能工牌分析、统计卡片、最近考试仅对学员显示 3. 快捷操作:根据角色显示不同的操作入口 4. 欢迎语:根据角色显示不同的欢迎信息 5. 学习天数:改为基于注册日期计算(至少为1天) 6. 成长路径:AI分析按钮仅对学员显示 Co-authored-by: Cursor --- backend/app/api/v1/users.py | 15 +++--- frontend/src/layout/index.vue | 23 +++++++- frontend/src/views/dashboard/index.vue | 62 +++++++++++++++++----- frontend/src/views/trainee/growth-path.vue | 11 +++- 4 files changed, 88 insertions(+), 23 deletions(-) diff --git a/backend/app/api/v1/users.py b/backend/app/api/v1/users.py index c28797a..adda791 100644 --- a/backend/app/api/v1/users.py +++ b/backend/app/api/v1/users.py @@ -47,20 +47,23 @@ async def get_current_user_statistics( 获取当前用户学习统计 返回字段: - - learningDays: 学习天数(按陪练会话开始日期去重) + - learningDays: 学习天数(从注册日期到今天的天数,至少为1) - totalHours: 学习总时长(小时,取整到1位小数) - practiceQuestions: 练习题数(答题记录条数汇总) - averageScore: 平均成绩(已提交考试的平均分,保留1位小数) - examsCompleted: 已完成考试数量 """ try: + from datetime import date user_id = current_user.id - # 学习天数:按会话开始日期去重 - learning_days_stmt = select(func.count(func.distinct(func.date(TrainingSession.start_time)))).where( - TrainingSession.user_id == user_id - ) - learning_days = (await db.scalar(learning_days_stmt)) or 0 + # 学习天数:从注册日期到今天的天数(至少为1天) + if current_user.created_at: + registration_date = current_user.created_at.date() if hasattr(current_user.created_at, 'date') else current_user.created_at + learning_days = (date.today() - registration_date).days + 1 # +1 是因为注册当天也算第1天 + learning_days = max(1, learning_days) # 确保至少为1 + else: + learning_days = 1 # 总时长(小时) total_seconds_stmt = select(func.coalesce(func.sum(TrainingSession.duration_seconds), 0)).where( diff --git a/frontend/src/layout/index.vue b/frontend/src/layout/index.vue index 7897b89..20c156b 100644 --- a/frontend/src/layout/index.vue +++ b/frontend/src/layout/index.vue @@ -379,6 +379,8 @@ const menuConfig = [ // 获取菜单路由 const menuRoutes = computed(() => { + const userRole = authManager.getUserRole() + // 仅保留当前用户可访问的菜单项和启用的功能 const filterChildren = (children: any[] = []) => children.filter((child: any) => { @@ -389,7 +391,22 @@ const menuRoutes = computed(() => { return true }) + // 根据角色预过滤顶级菜单 + const roleMenuFilter = (route: any): boolean => { + // 管理者中心:仅 admin 和 manager 可见 + if (route.path === '/manager') { + return userRole === 'admin' || userRole === 'manager' + } + // 系统管理:仅 admin 可见 + if (route.path === '/admin') { + return userRole === 'admin' + } + // 数据分析:所有登录用户可见(但子菜单会进一步过滤) + return true + } + return menuConfig + .filter(roleMenuFilter) // 先按角色过滤顶级菜单 .map((route: any) => { const next = { ...route } if (route.children && route.children.length > 0) { @@ -398,8 +415,10 @@ const menuRoutes = computed(() => { return next }) .filter((route: any) => { - // 有子菜单且至少一个可访问 - if (route.children && route.children.length > 0) return true + // 有子菜单的必须至少有一个可访问的子项 + if (route.children !== undefined) { + return route.children.length > 0 + } // 无子菜单时检查自身路径 return authManager.canAccessRoute(route.path) }) diff --git a/frontend/src/views/dashboard/index.vue b/frontend/src/views/dashboard/index.vue index 2700233..3ec3e1c 100644 --- a/frontend/src/views/dashboard/index.vue +++ b/frontend/src/views/dashboard/index.vue @@ -4,7 +4,15 @@

欢迎回来,{{ userName }}!

-

今天是您学习的第 {{ learningDays }} 天,继续加油!

+

+ 今天是您学习的第 {{ learningDays }} 天,继续加油! +

+

+ 管理您的团队,助力成员成长 +

+

+ 系统运行正常,一切尽在掌控 +

@@ -13,8 +21,8 @@
- -
+ +
@@ -24,7 +32,7 @@
{{ stat.value }}
{{ stat.title }}
-
+
@@ -48,8 +56,8 @@
- -
+ +

最近考试

@@ -99,6 +107,7 @@ const router = useRouter() // 获取当前用户信息 const currentUser = computed(() => authManager.getCurrentUser()) const userName = computed(() => currentUser.value?.full_name || currentUser.value?.username || '用户') +const userRole = computed(() => authManager.getUserRole()) const learningDays = ref(0) // 统计数据 @@ -156,37 +165,64 @@ const loadStatistics = async () => { } } -// 快捷操作 -const quickActions = ref([ +// 快捷操作配置(包含角色限制) +const allQuickActions = [ { title: '智能工牌分析', desc: 'AI能力评估与成长路径规划', icon: 'TrendCharts', color: '#e6a23c', - path: '/trainee/growth-path' + path: '/trainee/growth-path', + roles: ['trainee'] // 仅学员可见 }, { title: '课程中心', desc: '查看可用课程', icon: 'Collection', color: '#67c23a', - path: '/trainee/course-center' + path: '/trainee/course-center', + roles: ['trainee', 'manager', 'admin'] // 所有角色可见 }, { title: '查分中心', desc: '查看成绩和分析报告', icon: 'DataAnalysis', color: '#409eff', - path: '/trainee/score-report' + path: '/trainee/score-report', + roles: ['trainee'] // 仅学员可见 }, { title: 'AI陪练', desc: '智能陪练系统', icon: 'ChatLineRound', color: '#f56c6c', - path: '/trainee/ai-practice-center' + path: '/trainee/ai-practice-center', + roles: ['trainee', 'manager', 'admin'] // 所有角色可见 + }, + { + title: '团队看板', + desc: '查看团队学习情况', + icon: 'DataBoard', + color: '#667eea', + path: '/manager/team-dashboard', + roles: ['manager', 'admin'] // 管理者和管理员可见 + }, + { + title: '课程管理', + desc: '管理培训课程内容', + icon: 'Notebook', + color: '#909399', + path: '/manager/course-management', + roles: ['manager', 'admin'] // 管理者和管理员可见 } -]) +] + +// 根据角色过滤快捷操作 +const quickActions = computed(() => { + const role = userRole.value + if (!role) return [] + return allQuickActions.filter(action => action.roles.includes(role)) +}) // 最近考试 const recentExams = ref([]) diff --git a/frontend/src/views/trainee/growth-path.vue b/frontend/src/views/trainee/growth-path.vue index 4c6d839..c3c5d62 100644 --- a/frontend/src/views/trainee/growth-path.vue +++ b/frontend/src/views/trainee/growth-path.vue @@ -31,7 +31,14 @@

能力评估

- + + AI 分析智能工牌数据 @@ -302,7 +309,7 @@

{{ analyzing ? '正在分析您的智能工牌数据,为您推荐最适合的课程' : '暂无智能工牌数据,请先使用智能工牌记录对话' }}

- + 重新分析