1. 侧边栏:根据角色过滤菜单,无可访问子菜单时隐藏父菜单 2. Dashboard:智能工牌分析、统计卡片、最近考试仅对学员显示 3. 快捷操作:根据角色显示不同的操作入口 4. 欢迎语:根据角色显示不同的欢迎信息 5. 学习天数:改为基于注册日期计算(至少为1天) 6. 成长路径:AI分析按钮仅对学员显示 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -47,20 +47,23 @@ async def get_current_user_statistics(
|
|||||||
获取当前用户学习统计
|
获取当前用户学习统计
|
||||||
|
|
||||||
返回字段:
|
返回字段:
|
||||||
- learningDays: 学习天数(按陪练会话开始日期去重)
|
- learningDays: 学习天数(从注册日期到今天的天数,至少为1)
|
||||||
- totalHours: 学习总时长(小时,取整到1位小数)
|
- totalHours: 学习总时长(小时,取整到1位小数)
|
||||||
- practiceQuestions: 练习题数(答题记录条数汇总)
|
- practiceQuestions: 练习题数(答题记录条数汇总)
|
||||||
- averageScore: 平均成绩(已提交考试的平均分,保留1位小数)
|
- averageScore: 平均成绩(已提交考试的平均分,保留1位小数)
|
||||||
- examsCompleted: 已完成考试数量
|
- examsCompleted: 已完成考试数量
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
from datetime import date
|
||||||
user_id = current_user.id
|
user_id = current_user.id
|
||||||
|
|
||||||
# 学习天数:按会话开始日期去重
|
# 学习天数:从注册日期到今天的天数(至少为1天)
|
||||||
learning_days_stmt = select(func.count(func.distinct(func.date(TrainingSession.start_time)))).where(
|
if current_user.created_at:
|
||||||
TrainingSession.user_id == user_id
|
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 = (await db.scalar(learning_days_stmt)) or 0
|
learning_days = max(1, learning_days) # 确保至少为1
|
||||||
|
else:
|
||||||
|
learning_days = 1
|
||||||
|
|
||||||
# 总时长(小时)
|
# 总时长(小时)
|
||||||
total_seconds_stmt = select(func.coalesce(func.sum(TrainingSession.duration_seconds), 0)).where(
|
total_seconds_stmt = select(func.coalesce(func.sum(TrainingSession.duration_seconds), 0)).where(
|
||||||
|
|||||||
@@ -379,6 +379,8 @@ const menuConfig = [
|
|||||||
|
|
||||||
// 获取菜单路由
|
// 获取菜单路由
|
||||||
const menuRoutes = computed(() => {
|
const menuRoutes = computed(() => {
|
||||||
|
const userRole = authManager.getUserRole()
|
||||||
|
|
||||||
// 仅保留当前用户可访问的菜单项和启用的功能
|
// 仅保留当前用户可访问的菜单项和启用的功能
|
||||||
const filterChildren = (children: any[] = []) =>
|
const filterChildren = (children: any[] = []) =>
|
||||||
children.filter((child: any) => {
|
children.filter((child: any) => {
|
||||||
@@ -389,7 +391,22 @@ const menuRoutes = computed(() => {
|
|||||||
return true
|
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
|
return menuConfig
|
||||||
|
.filter(roleMenuFilter) // 先按角色过滤顶级菜单
|
||||||
.map((route: any) => {
|
.map((route: any) => {
|
||||||
const next = { ...route }
|
const next = { ...route }
|
||||||
if (route.children && route.children.length > 0) {
|
if (route.children && route.children.length > 0) {
|
||||||
@@ -398,8 +415,10 @@ const menuRoutes = computed(() => {
|
|||||||
return next
|
return next
|
||||||
})
|
})
|
||||||
.filter((route: any) => {
|
.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)
|
return authManager.canAccessRoute(route.path)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,7 +4,15 @@
|
|||||||
<div class="welcome-card card">
|
<div class="welcome-card card">
|
||||||
<div class="welcome-content">
|
<div class="welcome-content">
|
||||||
<h1 class="welcome-title">欢迎回来,{{ userName }}!</h1>
|
<h1 class="welcome-title">欢迎回来,{{ userName }}!</h1>
|
||||||
<p class="welcome-desc">今天是您学习的第 <span class="highlight">{{ learningDays }}</span> 天,继续加油!</p>
|
<p class="welcome-desc" v-if="userRole === 'trainee'">
|
||||||
|
今天是您学习的第 <span class="highlight">{{ learningDays }}</span> 天,继续加油!
|
||||||
|
</p>
|
||||||
|
<p class="welcome-desc" v-else-if="userRole === 'manager'">
|
||||||
|
管理您的团队,助力成员成长
|
||||||
|
</p>
|
||||||
|
<p class="welcome-desc" v-else>
|
||||||
|
系统运行正常,一切尽在掌控
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="welcome-image">
|
<div class="welcome-image">
|
||||||
<el-icon :size="120" color="#667eea">
|
<el-icon :size="120" color="#667eea">
|
||||||
@@ -13,8 +21,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 统计卡片 -->
|
<!-- 统计卡片 - 仅学员显示 -->
|
||||||
<div class="stats-grid">
|
<div class="stats-grid" v-if="userRole === 'trainee'">
|
||||||
<div class="stat-card card" v-for="stat in stats" :key="stat.title">
|
<div class="stat-card card" v-for="stat in stats" :key="stat.title">
|
||||||
<div class="stat-icon" :style="{ backgroundColor: stat.bgColor }">
|
<div class="stat-icon" :style="{ backgroundColor: stat.bgColor }">
|
||||||
<el-icon :size="24" :color="stat.color">
|
<el-icon :size="24" :color="stat.color">
|
||||||
@@ -24,7 +32,7 @@
|
|||||||
<div class="stat-content">
|
<div class="stat-content">
|
||||||
<div class="stat-value">{{ stat.value }}</div>
|
<div class="stat-value">{{ stat.value }}</div>
|
||||||
<div class="stat-title">{{ stat.title }}</div>
|
<div class="stat-title">{{ stat.title }}</div>
|
||||||
<div class="stat-trend" :class="stat.trend > 0 ? 'up' : 'down'">
|
<div class="stat-trend" :class="stat.trend > 0 ? 'up' : 'down'" v-if="stat.trend !== 0">
|
||||||
<el-icon :size="12">
|
<el-icon :size="12">
|
||||||
<component :is="stat.trend > 0 ? 'Top' : 'Bottom'" />
|
<component :is="stat.trend > 0 ? 'Top' : 'Bottom'" />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
@@ -48,8 +56,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 最近考试 -->
|
<!-- 最近考试 - 仅学员显示 -->
|
||||||
<div class="recent-exams">
|
<div class="recent-exams" v-if="userRole === 'trainee'">
|
||||||
<h2 class="section-title">最近考试</h2>
|
<h2 class="section-title">最近考试</h2>
|
||||||
<div v-if="recentExams.length > 0" class="exam-list">
|
<div v-if="recentExams.length > 0" class="exam-list">
|
||||||
<div class="exam-item card" v-for="exam in recentExams" :key="exam.id">
|
<div class="exam-item card" v-for="exam in recentExams" :key="exam.id">
|
||||||
@@ -99,6 +107,7 @@ const router = useRouter()
|
|||||||
// 获取当前用户信息
|
// 获取当前用户信息
|
||||||
const currentUser = computed(() => authManager.getCurrentUser())
|
const currentUser = computed(() => authManager.getCurrentUser())
|
||||||
const userName = computed(() => currentUser.value?.full_name || currentUser.value?.username || '用户')
|
const userName = computed(() => currentUser.value?.full_name || currentUser.value?.username || '用户')
|
||||||
|
const userRole = computed(() => authManager.getUserRole())
|
||||||
const learningDays = ref(0)
|
const learningDays = ref(0)
|
||||||
|
|
||||||
// 统计数据
|
// 统计数据
|
||||||
@@ -156,37 +165,64 @@ const loadStatistics = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 快捷操作
|
// 快捷操作配置(包含角色限制)
|
||||||
const quickActions = ref([
|
const allQuickActions = [
|
||||||
{
|
{
|
||||||
title: '智能工牌分析',
|
title: '智能工牌分析',
|
||||||
desc: 'AI能力评估与成长路径规划',
|
desc: 'AI能力评估与成长路径规划',
|
||||||
icon: 'TrendCharts',
|
icon: 'TrendCharts',
|
||||||
color: '#e6a23c',
|
color: '#e6a23c',
|
||||||
path: '/trainee/growth-path'
|
path: '/trainee/growth-path',
|
||||||
|
roles: ['trainee'] // 仅学员可见
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '课程中心',
|
title: '课程中心',
|
||||||
desc: '查看可用课程',
|
desc: '查看可用课程',
|
||||||
icon: 'Collection',
|
icon: 'Collection',
|
||||||
color: '#67c23a',
|
color: '#67c23a',
|
||||||
path: '/trainee/course-center'
|
path: '/trainee/course-center',
|
||||||
|
roles: ['trainee', 'manager', 'admin'] // 所有角色可见
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '查分中心',
|
title: '查分中心',
|
||||||
desc: '查看成绩和分析报告',
|
desc: '查看成绩和分析报告',
|
||||||
icon: 'DataAnalysis',
|
icon: 'DataAnalysis',
|
||||||
color: '#409eff',
|
color: '#409eff',
|
||||||
path: '/trainee/score-report'
|
path: '/trainee/score-report',
|
||||||
|
roles: ['trainee'] // 仅学员可见
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'AI陪练',
|
title: 'AI陪练',
|
||||||
desc: '智能陪练系统',
|
desc: '智能陪练系统',
|
||||||
icon: 'ChatLineRound',
|
icon: 'ChatLineRound',
|
||||||
color: '#f56c6c',
|
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<any[]>([])
|
const recentExams = ref<any[]>([])
|
||||||
|
|||||||
@@ -31,7 +31,14 @@
|
|||||||
<div class="ability-radar card">
|
<div class="ability-radar card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title">能力评估</h3>
|
<h3 class="card-title">能力评估</h3>
|
||||||
<el-button type="primary" size="small" @click="analyzeSmartBadgeData" :loading="analyzing">
|
<!-- AI智能工牌分析仅对学员开放 -->
|
||||||
|
<el-button
|
||||||
|
v-if="userInfo.role === 'trainee'"
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
@click="analyzeSmartBadgeData"
|
||||||
|
:loading="analyzing"
|
||||||
|
>
|
||||||
<el-icon><TrendCharts /></el-icon>
|
<el-icon><TrendCharts /></el-icon>
|
||||||
AI 分析智能工牌数据
|
AI 分析智能工牌数据
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -302,7 +309,7 @@
|
|||||||
<p class="empty-description">
|
<p class="empty-description">
|
||||||
{{ analyzing ? '正在分析您的智能工牌数据,为您推荐最适合的课程' : '暂无智能工牌数据,请先使用智能工牌记录对话' }}
|
{{ analyzing ? '正在分析您的智能工牌数据,为您推荐最适合的课程' : '暂无智能工牌数据,请先使用智能工牌记录对话' }}
|
||||||
</p>
|
</p>
|
||||||
<el-button v-if="!analyzing" type="primary" @click="analyzeSmartBadgeData">
|
<el-button v-if="!analyzing && userInfo.role === 'trainee'" type="primary" @click="analyzeSmartBadgeData">
|
||||||
<el-icon><Refresh /></el-icon>
|
<el-icon><Refresh /></el-icon>
|
||||||
重新分析
|
重新分析
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|||||||
Reference in New Issue
Block a user