Files
012-kaopeilian/frontend/src/views/trainee/growth-path.vue
yuliang_guo b4906c543b
All checks were successful
continuous-integration/drone/push Build is passing
feat: 实现成长路径功能
- 新增数据库表: growth_path_nodes, user_growth_path_progress, user_node_completions
- 新增 Model: GrowthPathNode, UserGrowthPathProgress, UserNodeCompletion
- 新增 Service: GrowthPathService(管理端CRUD、学员端进度追踪)
- 新增 API: 学员端获取成长路径、管理端CRUD
- 前端学员端从API动态加载成长路径数据
- 更新管理端API接口定义
2026-01-30 15:37:14 +08:00

2379 lines
65 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="growth-path-container">
<!-- 个人信息栏 -->
<div class="personal-info card">
<div class="info-left">
<el-avatar :size="80" :src="userInfo.avatar">
<el-icon :size="40"><UserFilled /></el-icon>
</el-avatar>
<div class="info-content">
<h2 class="user-name">{{ userInfo.name }}</h2>
<div class="user-meta">
<span class="position">{{ userInfo.position }}</span>
<span class="separator">|</span>
<span class="level">Lv.{{ userInfo.level }}</span>
<span class="separator">|</span>
<span class="exp">经验值{{ userInfo.exp }}/{{ userInfo.nextLevelExp }}</span>
</div>
<el-progress :percentage="expPercentage" :stroke-width="10" />
</div>
</div>
<div class="info-right">
<el-button type="primary" @click="viewProfile">
<el-icon class="el-icon--left"><User /></el-icon>
个人中心
</el-button>
</div>
</div>
<div class="main-content">
<!-- 能力雷达图 -->
<div class="ability-radar card">
<div class="card-header">
<h3 class="card-title">能力评估</h3>
<el-button type="primary" size="small" @click="analyzeSmartBadgeData" :loading="analyzing">
<el-icon><TrendCharts /></el-icon>
AI 分析智能工牌数据
</el-button>
</div>
<div class="radar-chart" ref="radarChartRef"></div>
<!-- AI 能力分析详细反馈 -->
<div v-if="abilityFeedback.length > 0" class="ability-feedback">
<div class="feedback-header">
<el-icon><MagicStick /></el-icon>
<span>AI 详细分析</span>
</div>
<div class="feedback-list">
<div
v-for="item in abilityFeedback"
:key="item.name"
class="feedback-item"
:class="{ 'weak': item.score < 80, 'good': item.score >= 80 && item.score < 90, 'excellent': item.score >= 90 }"
>
<div class="feedback-header-row">
<span class="dimension-name">{{ item.name }}</span>
<span class="dimension-score">{{ item.score }}</span>
</div>
<p class="feedback-text">{{ item.feedback }}</p>
</div>
</div>
</div>
</div>
<!-- AI 智能推荐学习模块 -->
<div class="ai-learning-hub-inner">
<!-- 模块头部 -->
<div class="hub-header">
<div class="header-left">
<div class="ai-avatar">
<div class="avatar-glow"></div>
<el-icon :size="28"><Cpu /></el-icon>
</div>
<div class="header-text">
<h2 class="hub-title">AI 智能学习助手</h2>
<p class="hub-subtitle">基于能力评估的个性化课程推荐</p>
</div>
</div>
<div class="header-actions">
<el-button
type="primary"
:loading="analyzing"
@click="refreshRecommendations"
class="refresh-btn"
>
<el-icon><MagicStick /></el-icon>
重新分析
</el-button>
</div>
</div>
<!-- 推荐统计 -->
<div class="recommendation-stats">
<div class="stat-card">
<div class="stat-icon">
<el-icon><TrendCharts /></el-icon>
</div>
<div class="stat-content">
<div class="stat-number">{{ recommendedCourses.length }}</div>
<div class="stat-label">推荐课程</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">
<el-icon><Trophy /></el-icon>
</div>
<div class="stat-content">
<div class="stat-number">{{ getAverageImprovement() }}</div>
<div class="stat-label">平均提升</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">
<el-icon><Clock /></el-icon>
</div>
<div class="stat-content">
<div class="stat-number">{{ getTotalHours() }}h</div>
<div class="stat-label">总学时</div>
</div>
</div>
</div>
<!-- 推荐课程列表 -->
<div class="recommendations-section">
<div class="section-header">
<h3 class="section-title">为您量身定制的学习路径</h3>
<div class="filter-tabs">
<el-radio-group v-model="selectedPriority" size="small" @change="filterByPriority">
<el-radio-button label="all">全部</el-radio-button>
<el-radio-button label="high">高优先级</el-radio-button>
<el-radio-button label="medium">中优先级</el-radio-button>
</el-radio-group>
</div>
</div>
<!-- 课程卡片网格 -->
<div class="course-grid">
<div
v-for="(course, index) in filteredCourses"
:key="course.id"
class="smart-course-card"
:class="{ 'featured': course.priority === 'high' }"
:style="{ '--delay': index * 0.1 + 's' }"
@click="viewCourseDetail(course)"
>
<!-- 卡片装饰 -->
<div class="card-decoration">
<div class="decoration-line"></div>
<div class="priority-indicator" :class="course.priority">
<el-icon><Star /></el-icon>
</div>
</div>
<!-- 课程封面 -->
<div class="smart-cover">
<div class="cover-gradient"></div>
<div class="cover-content">
<div class="course-icon">
<el-icon :size="40"><Document /></el-icon>
</div>
<div class="match-score">
<div class="score-ring">
<svg class="score-circle" viewBox="0 0 36 36">
<path class="circle-bg" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
<path class="circle" :stroke-dasharray="`${course.matchScore}, 100`" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
</svg>
<div class="score-text">{{ course.matchScore }}%</div>
</div>
<div class="score-label">匹配度</div>
</div>
</div>
</div>
<!-- 课程信息 -->
<div class="smart-info">
<div class="course-header">
<h4 class="course-title">{{ course.name }}</h4>
<div class="priority-tag" :class="course.priority">
{{ getPriorityText(course.priority) }}
</div>
</div>
<p class="course-description">{{ course.description }}</p>
<!-- AI 分析结果 -->
<div class="ai-analysis">
<div class="analysis-header">
<el-icon><MagicStick /></el-icon>
<span>AI 分析结果</span>
</div>
<div class="analysis-content">
<div class="weakness-tags">
<el-tag
v-for="weakness in course.targetWeakPoints"
:key="weakness"
type="warning"
size="small"
effect="light"
>
<el-icon><Warning /></el-icon>
{{ weakness }}
</el-tag>
</div>
<div class="improvement-badge">
<el-icon><TrendCharts /></el-icon>
<span>预期提升 +{{ course.expectedImprovement }}</span>
</div>
</div>
</div>
<!-- 课程元数据 -->
<div class="course-metadata">
<div class="meta-item">
<el-icon><Timer /></el-icon>
<span>{{ course.duration }}小时</span>
</div>
<div class="meta-item">
<el-icon><Medal /></el-icon>
<span>{{ getDifficultyText(course.difficulty) }}</span>
</div>
<div class="meta-item">
<el-icon><UserFilled /></el-icon>
<span>{{ course.learnerCount }}人在学</span>
</div>
</div>
<!-- 学习进度条 -->
<div class="progress-section" v-if="course.progress > 0">
<div class="progress-header">
<span class="progress-label">学习进度</span>
<span class="progress-value">{{ course.progress }}%</span>
</div>
<el-progress
:percentage="course.progress"
:stroke-width="6"
:show-text="false"
color="#667eea"
/>
</div>
<!-- 智能操作按钮 -->
<div class="smart-actions">
<el-button
type="primary"
class="primary-action"
@click.stop="enterCourse(course)"
>
<el-icon><VideoPlay /></el-icon>
进入学习
</el-button>
<div class="action-grid">
<!-- 播课功能暂时关闭 -->
<el-button
v-if="false"
class="action-btn"
@click.stop="playAudio(course)"
>
<el-icon><Headset /></el-icon>
播课
</el-button>
<el-button
class="action-btn"
@click.stop="chatWithCourse(course)"
>
<el-icon><ChatDotRound /></el-icon>
对话
</el-button>
<el-button
v-if="course.progress >= 80"
class="action-btn exam-btn"
@click.stop="startExam(course)"
>
<el-icon><Edit /></el-icon>
考试
</el-button>
<el-button
v-if="course.examPassed"
class="action-btn practice-btn"
@click.stop="startPractice(course)"
>
<el-icon><Microphone /></el-icon>
陪练
</el-button>
</div>
</div>
</div>
</div>
</div>
<!-- 空状态 -->
<div v-if="filteredCourses.length === 0" class="empty-recommendations">
<div class="empty-icon">
<el-icon :size="64" :class="{ 'rotating': analyzing }">
<MagicStick v-if="!analyzing" />
<Loading v-else />
</el-icon>
</div>
<h3 class="empty-title">{{ analyzing ? 'AI 正在分析中...' : '暂无推荐课程' }}</h3>
<p class="empty-description">
{{ analyzing ? '正在分析您的智能工牌数据,为您推荐最适合的课程' : '暂无智能工牌数据,请先使用智能工牌记录对话' }}
</p>
<el-button v-if="!analyzing" type="primary" @click="analyzeSmartBadgeData">
<el-icon><Refresh /></el-icon>
重新分析
</el-button>
</div>
</div>
</div>
</div>
<!-- 成长路径图 -->
<div class="growth-tree card">
<div class="card-header">
<h3 class="card-title">我的成长路径</h3>
<div class="legend">
<span class="legend-item completed">
<i class="dot"></i>已完成
</span>
<span class="legend-item current">
<i class="dot"></i>当前任务
</span>
<span class="legend-item locked">
<i class="dot"></i>未解锁
</span>
</div>
</div>
<div class="tree-container">
<div class="tree-level" v-for="(level, index) in growthPath" :key="index">
<div class="level-header">
<span class="level-name">{{ level.name }}</span>
<span class="level-progress">{{ level.completed }}/{{ level.total }}</span>
</div>
<div class="level-nodes">
<div
class="tree-node"
:class="node.status"
v-for="node in level.nodes"
:key="node.id"
@click="handleNodeClick(node)"
>
<div class="node-icon">
<el-icon :size="24">
<component :is="getNodeIcon(node.status)" />
</el-icon>
</div>
<div class="node-content">
<h4 class="node-title">{{ node.title }}</h4>
<p class="node-desc">{{ node.description }}</p>
<div class="node-progress" v-if="node.status === 'current'">
<el-progress :percentage="node.progress" :stroke-width="6" />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, nextTick, watch } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import {
UserFilled,
User,
TrendCharts,
MagicStick, // AI分析图标
Cpu // 机器人图标
} from '@element-plus/icons-vue'
import * as echarts from 'echarts'
import { analyzeYanjiBadge, getCourseDetail, getGrowthPath, startGrowthPath } from '@/api/trainee'
import type { TraineeGrowthPath, TraineeGrowthPathStage } from '@/api/trainee'
import { getCurrentUserProfile } from '@/api/user'
const router = useRouter()
// 雷达图引用
const radarChartRef = ref()
let radarChart: any = null
// 能力数据
const abilityData = ref([
{ name: '专业知识', value: 88, max: 100 },
{ name: '沟通技巧', value: 92, max: 100 },
{ name: '操作技能', value: 85, max: 100 },
{ name: '客户服务', value: 90, max: 100 },
{ name: '安全意识', value: 83, max: 100 },
{ name: '应变能力', value: 80, max: 100 }
])
// 定义能力反馈接口
interface AbilityFeedback {
name: string
score: number
feedback: string
}
// AI 能力分析详细反馈来自Dify
const abilityFeedback = ref<AbilityFeedback[]>([])
// AI 分析和推荐相关
const analyzing = ref(false)
const selectedPriority = ref('all')
// 计算属性:过滤后的课程
const filteredCourses = computed(() => {
if (selectedPriority.value === 'all') {
return recommendedCourses.value
}
return recommendedCourses.value.filter(course => course.priority === selectedPriority.value)
})
// 定义推荐课程接口
interface RecommendedCourse {
id: number
name: string
description: string
duration: number
difficulty: string
learnerCount: number
priority: string
matchScore: number
targetWeakPoints: string[]
expectedImprovement: number
progress: number
examPassed: boolean
}
// AI 推荐课程数据初始为空等待AI分析后填充
const recommendedCourses = ref<RecommendedCourse[]>([])
// 用户信息
const userInfo = ref({
name: '加载中...',
position: '加载中...',
level: 1,
exp: 0,
nextLevelExp: 1000,
avatar: '',
role: 'trainee', // 用户角色
phone: '' // 用户手机号
})
// 经验值百分比
const expPercentage = computed(() => {
return Math.round((userInfo.value.exp / userInfo.value.nextLevelExp) * 100)
})
// 成长路径数据从API加载
const growthPathData = ref<TraineeGrowthPath | null>(null)
const growthPathLoading = ref(false)
// 将API数据转换为前端展示格式
const growthPath = computed(() => {
if (!growthPathData.value || !growthPathData.value.stages) {
// 返回默认空数据或占位数据
return [{
name: '暂无成长路径',
completed: 0,
total: 0,
nodes: []
}]
}
return growthPathData.value.stages.map(stage => ({
name: stage.name,
completed: stage.completed,
total: stage.total,
nodes: stage.nodes.map(node => ({
id: node.id,
courseId: node.course_id,
title: node.title,
description: node.description || '',
status: node.status, // locked/unlocked/in_progress/completed
progress: node.progress
}))
}))
})
/**
* 加载成长路径数据
*/
const loadGrowthPath = async () => {
growthPathLoading.value = true
try {
const response = await getGrowthPath()
if (response.code === 200 && response.data) {
growthPathData.value = response.data
}
} catch (error) {
console.error('加载成长路径失败:', error)
// 失败时不显示错误,保持空状态
} finally {
growthPathLoading.value = false
}
}
/**
* 开始学习成长路径
*/
const handleStartGrowthPath = async () => {
if (!growthPathData.value) return
try {
const response = await startGrowthPath(growthPathData.value.id)
if (response.code === 200) {
ElMessage.success('已开始学习成长路径')
// 重新加载数据
await loadGrowthPath()
}
} catch (error: any) {
console.error('开始学习失败:', error)
ElMessage.error(error.response?.data?.detail || '开始学习失败')
}
}
/**
* 初始化雷达图
*/
const initRadarChart = () => {
if (!radarChartRef.value) return
if (!radarChart) {
radarChart = echarts.init(radarChartRef.value)
}
const option = {
title: {
text: '能力评估雷达图',
left: 'center',
textStyle: {
fontSize: 16,
fontWeight: 'normal',
color: '#333'
}
},
radar: {
indicator: abilityData.value.map(item => ({
name: item.name,
max: item.max
})),
center: ['50%', '55%'],
radius: '65%',
axisName: {
fontSize: 12,
color: '#666'
},
splitArea: {
areaStyle: {
color: ['rgba(114, 172, 209, 0.2)', 'rgba(114, 172, 209, 0.4)']
}
}
},
series: [{
name: '能力评估',
type: 'radar',
data: [{
value: abilityData.value.map(item => item.value),
name: '当前能力',
areaStyle: {
color: 'rgba(103, 194, 58, 0.3)'
},
lineStyle: {
color: '#67c23a',
width: 2
},
itemStyle: {
color: '#67c23a'
}
}]
}]
}
radarChart.setOption(option)
}
/**
* AI 分析智能工牌数据
*/
const analyzeSmartBadgeData = async () => {
// 前置检查:只有学员用户且已绑定手机号才能分析
if (userInfo.value.role !== 'trainee') {
ElMessage.warning('该功能仅对学员开放')
return
}
if (!userInfo.value.phone) {
ElMessage.warning('请先在个人中心绑定手机号,以便使用智能工牌数据分析功能')
return
}
analyzing.value = true
try {
// 调用后端API分析智能工牌数据
const response = await analyzeYanjiBadge()
if (response.code === 200 && response.data) {
const { dimensions, recommended_courses, total_score, conversation_count } = response.data
// 更新能力雷达图数据
abilityData.value = dimensions.map(dim => ({
name: dim.name,
value: dim.score,
max: 100
}))
// 保存AI分析反馈包含详细的分析意见
abilityFeedback.value = dimensions
// 重新初始化雷达图
initRadarChart()
// 更新推荐课程列表
// 从Dify获取推荐后查询课程详情补充完整信息
const coursePromises = recommended_courses.map(async (rec) => {
try {
// 查询课程详情
const courseResponse = await getCourseDetail(rec.course_id)
const courseDetail = courseResponse.data
return {
id: rec.course_id,
name: rec.course_name,
description: rec.recommendation_reason, // 使用AI推荐理由作为描述
duration: courseDetail?.duration_hours || 0,
difficulty: courseDetail?.difficulty_level || 'intermediate',
learnerCount: courseDetail?.learner_count || 0,
priority: rec.priority,
matchScore: rec.match_score,
targetWeakPoints: [], // TODO: 从recommendation_reason中提取
expectedImprovement: 10, // TODO: 从recommendation_reason中提取
progress: 0,
examPassed: false
}
} catch (error) {
console.error(`查询课程${rec.course_id}详情失败:`, error)
// 失败时使用基本信息
return {
id: rec.course_id,
name: rec.course_name,
description: rec.recommendation_reason,
duration: 0,
difficulty: 'intermediate',
learnerCount: 0,
priority: rec.priority,
matchScore: rec.match_score,
targetWeakPoints: [],
expectedImprovement: 10,
progress: 0,
examPassed: false
}
}
})
recommendedCourses.value = await Promise.all(coursePromises)
ElMessage.success(
`智能工牌数据分析完成!分析了${conversation_count}条对话记录,综合评分:${total_score}`
)
} else {
throw new Error(response.message || '分析失败')
}
} catch (error: any) {
console.error('智能工牌数据分析失败:', error)
// 根据错误类型显示不同提示
if (error.response?.status === 404) {
ElMessage.warning('暂无智能工牌数据,请先使用智能工牌记录对话')
} else if (error.response?.status === 400) {
ElMessage.warning('用户未绑定手机号,无法匹配智能工牌数据')
} else {
ElMessage.error('AI分析失败请稍后重试')
}
// 失败时使用模拟数据作为兜底
abilityData.value = [
{ name: '专业知识', value: 85, max: 100 },
{ name: '沟通技巧', value: 88, max: 100 },
{ name: '操作技能', value: 82, max: 100 },
{ name: '客户服务', value: 90, max: 100 },
{ name: '安全意识', value: 79, max: 100 },
{ name: '应变能力', value: 76, max: 100 }
]
initRadarChart()
} finally {
analyzing.value = false
}
}
/**
* 刷新AI推荐课程
* 重新调用AI分析接口获取最新的课程推荐
*/
const refreshRecommendations = async () => {
// 直接调用AI分析智能工牌数据的方法复用已有逻辑
await analyzeSmartBadgeData()
}
/**
* 获取节点图标
*/
const getNodeIcon = (status: string) => {
switch (status) {
case 'completed':
return 'CircleCheck'
case 'current':
return 'Clock'
case 'locked':
return 'Lock'
default:
return 'Clock'
}
}
/**
* 处理节点点击
*/
const handleNodeClick = (node: any) => {
if (node.status === 'locked') {
ElMessage.warning('请先完成前置课程')
return
}
// 跳转到课程详情页
if (node.courseId) {
router.push(`/trainee/course-detail?id=${node.courseId}`)
} else {
ElMessage.info(`${node.title}`)
}
}
/**
* 跳转到个人中心
*/
const viewProfile = () => {
router.push('/user/profile')
}
/**
* 获取优先级文本
*/
const getPriorityText = (priority: string) => {
const textMap: Record<string, string> = {
high: '高优先级',
medium: '中优先级',
low: '低优先级'
}
return textMap[priority] || priority
}
/**
* 获取难度文本
*/
const getDifficultyText = (difficulty: string) => {
const textMap: Record<string, string> = {
easy: '简单',
medium: '中等',
hard: '困难'
}
return textMap[difficulty] || difficulty
}
/**
* 查看课程详情
*/
const viewCourseDetail = (course: any) => {
ElMessage.info(`查看课程详情:${course.name}`)
}
/**
* 进入课程
*/
const enterCourse = (course: any) => {
router.push('/trainee/course-detail?id=' + course.id)
}
/**
* 播放音频
*/
const playAudio = (course: any) => {
router.push('/trainee/audio-player?courseId=' + course.id)
}
/**
* 与课程对话
*/
const chatWithCourse = (course: any) => {
router.push('/trainee/chat-course?courseId=' + course.id)
}
/**
* 开始考试
*/
const startExam = (course: any) => {
if (course.progress < 80) {
ElMessage.warning('请先完成80%的学习进度')
return
}
router.push('/trainee/exam?courseId=' + course.id)
}
/**
* 开始陪练
*/
const startPractice = (course: any) => {
if (!course.examPassed) {
ElMessage.warning('请先通过考试')
return
}
router.push('/trainee/ai-practice?courseId=' + course.id)
}
/**
* 获取平均提升分数
*/
const getAverageImprovement = () => {
if (recommendedCourses.value.length === 0) return 0
const total = recommendedCourses.value.reduce((sum, course) => sum + course.expectedImprovement, 0)
return Math.round(total / recommendedCourses.value.length)
}
/**
* 获取总学时
*/
const getTotalHours = () => {
return recommendedCourses.value.reduce((sum, course) => sum + course.duration, 0)
}
/**
* 按优先级筛选
*/
const filterByPriority = () => {
// 筛选逻辑在计算属性中处理
}
/**
* 获取当前用户信息
*/
const fetchUserInfo = async () => {
try {
const response = await getCurrentUserProfile()
if (response.code === 200 && response.data) {
const user = response.data
userInfo.value = {
name: user.full_name || user.username || '未命名',
position: user.position_name || (user.role === 'admin' ? '管理员' : user.role === 'trainer' ? '培训师' : '学员'),
level: 1,
exp: 0,
nextLevelExp: 1000,
avatar: user.avatar_url || '',
role: user.role || 'trainee',
phone: user.phone || ''
}
// 获取等级信息
try {
const { getMyLevel } = await import('@/api/level')
const levelResponse = await getMyLevel()
if (levelResponse.code === 200 && levelResponse.data) {
const levelData = levelResponse.data
userInfo.value.level = levelData.level
userInfo.value.exp = levelData.total_exp
userInfo.value.nextLevelExp = levelData.next_level_exp || 1000
}
} catch (levelError) {
console.warn('获取等级信息失败:', levelError)
}
}
} catch (error) {
console.error('获取用户信息失败:', error)
ElMessage.warning('获取用户信息失败,使用默认信息')
}
}
/**
* 处理窗口大小变化
*/
const handleResize = () => {
if (radarChart) {
radarChart.resize()
}
}
/**
* 组件挂载时初始化
*/
onMounted(async () => {
// 获取用户信息
await fetchUserInfo()
// 加载成长路径数据
await loadGrowthPath()
nextTick(() => {
initRadarChart()
})
// 只有学员用户且已绑定手机号才自动分析智能工牌数据
if (userInfo.value.role === 'trainee' && userInfo.value.phone) {
analyzeSmartBadgeData()
} else if (!userInfo.value.phone && userInfo.value.role === 'trainee') {
// 学员未绑定手机号时显示提示
ElMessage.info('请先在个人中心绑定手机号,以便使用智能工牌数据分析功能')
}
// 响应式处理
window.addEventListener('resize', handleResize)
})
/**
* 组件卸载时清理资源
*/
onUnmounted(() => {
// 销毁图表实例
radarChart?.dispose()
// 移除事件监听
window.removeEventListener('resize', handleResize)
})
</script>
<style lang="scss" scoped>
.growth-path-container {
max-width: 1400px;
margin: 0 auto;
.personal-info {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24px;
margin-bottom: 24px;
.info-left {
display: flex;
gap: 24px;
align-items: center;
.info-content {
.user-name {
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 8px;
}
.user-meta {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
color: #666;
margin-bottom: 12px;
.separator {
color: #ddd;
}
.position {
color: #667eea;
font-weight: 500;
}
.level {
color: #e6a23c;
font-weight: 500;
}
}
.el-progress {
width: 300px;
}
}
}
.info-right {
.el-button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
}
}
}
.main-content {
display: grid;
grid-template-columns: 400px 1fr;
gap: 24px;
margin-bottom: 24px;
.ability-radar {
height: fit-content;
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 24px;
border-bottom: 1px solid #ebeef5;
.card-title {
font-size: 18px;
font-weight: 600;
color: #333;
}
}
.radar-chart {
padding: 24px;
height: 350px;
width: 100%;
}
.ability-feedback {
padding: 24px;
border-top: 1px solid #f0f0f0;
.feedback-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
font-size: 16px;
font-weight: 600;
color: #333;
.el-icon {
color: #667eea;
}
}
.feedback-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.feedback-item {
padding: 16px;
border-radius: 8px;
background: #fafafa;
border-left: 4px solid #94a3b8;
transition: all 0.3s ease;
&:hover {
background: #f5f5f5;
transform: translateX(4px);
}
&.weak {
border-left-color: #ef4444;
background: #fef2f2;
&:hover {
background: #fee2e2;
}
}
&.good {
border-left-color: #f59e0b;
background: #fffbeb;
&:hover {
background: #fef3c7;
}
}
&.excellent {
border-left-color: #10b981;
background: #f0fdf4;
&:hover {
background: #dcfce7;
}
}
.feedback-header-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
.dimension-name {
font-size: 15px;
font-weight: 600;
color: #333;
}
.dimension-score {
font-size: 14px;
font-weight: 600;
color: #667eea;
}
}
.feedback-text {
font-size: 13px;
line-height: 1.6;
color: #666;
margin: 0;
}
}
}
}
}
// 成长路径卡片样式
.growth-tree {
margin-top: 24px;
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 24px;
border-bottom: 1px solid #ebeef5;
.card-title {
font-size: 18px;
font-weight: 600;
color: #333;
}
.legend {
display: flex;
gap: 16px;
.legend-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: #666;
.dot {
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
}
&.completed .dot {
background: #67c23a;
}
&.current .dot {
background: #409eff;
}
&.locked .dot {
background: #c0c4cc;
}
}
}
}
.tree-container {
padding: 24px;
.tree-level {
margin-bottom: 32px;
&:last-child {
margin-bottom: 0;
}
.level-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding: 14px 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 10px;
box-shadow: 0 4px 10px rgba(102, 126, 234, 0.2);
.level-name {
font-size: 17px;
font-weight: 600;
color: #fff;
}
.level-progress {
font-size: 14px;
color: rgba(255, 255, 255, 0.9);
background: rgba(255, 255, 255, 0.2);
padding: 4px 12px;
border-radius: 12px;
}
}
.level-nodes {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
.tree-node {
padding: 24px;
border: 2px solid #e4e7ed;
border-radius: 12px;
background: #fff;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
background: #409eff;
transform: scaleY(0);
transition: transform 0.3s ease;
}
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
&::before {
transform: scaleY(1);
}
}
&.completed {
border-color: #67c23a;
background: linear-gradient(135deg, rgba(103, 194, 58, 0.05) 0%, rgba(103, 194, 58, 0.02) 100%);
&::before {
background: #67c23a;
}
}
&.current {
border-color: #409eff;
background: linear-gradient(135deg, rgba(64, 158, 255, 0.08) 0%, rgba(64, 158, 255, 0.03) 100%);
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2);
&::before {
background: #409eff;
transform: scaleY(1);
}
}
&.locked {
border-color: #dcdfe6;
background: #f9fafb;
cursor: not-allowed;
opacity: 0.7;
&:hover {
transform: none;
box-shadow: none;
}
&::before {
background: #c0c4cc;
}
}
.node-icon {
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
border-radius: 12px;
background: rgba(64, 158, 255, 0.1);
color: #409eff;
margin-bottom: 16px;
transition: all 0.3s ease;
}
&.completed .node-icon {
background: rgba(103, 194, 58, 0.1);
color: #67c23a;
}
&.locked .node-icon {
background: rgba(192, 196, 204, 0.1);
color: #c0c4cc;
}
&:hover .node-icon {
transform: scale(1.1) rotate(5deg);
}
.node-content {
.node-title {
font-size: 17px;
font-weight: 600;
color: #303133;
margin-bottom: 10px;
line-height: 1.4;
}
.node-desc {
font-size: 14px;
color: #606266;
line-height: 1.6;
margin-bottom: 16px;
}
.node-progress {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid #f0f0f0;
}
}
}
}
}
}
}
// AI 智能学习助手样式
.ai-learning-hub-inner {
margin-bottom: 32px;
background: linear-gradient(135deg, rgba(255,255,255,0.95) 0%, rgba(248,250,252,0.9) 100%);
border-radius: 24px;
padding: 32px;
box-shadow: 0 20px 40px rgba(0,0,0,0.08);
border: 1px solid rgba(255,255,255,0.2);
backdrop-filter: blur(20px);
position: relative;
overflow: hidden;
&:before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
radial-gradient(circle at 20% 10%, rgba(102,126,234,0.1) 0%, transparent 40%),
radial-gradient(circle at 80% 90%, rgba(118,75,162,0.08) 0%, transparent 40%);
pointer-events: none;
z-index: 0;
}
> * {
position: relative;
z-index: 1;
}
// 模块头部样式
.hub-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32px;
.header-left {
display: flex;
align-items: center;
gap: 16px;
.ai-avatar {
position: relative;
width: 60px;
height: 60px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
.avatar-glow {
position: absolute;
inset: -4px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
opacity: 0.3;
filter: blur(8px);
animation: pulse 2s ease-in-out infinite;
}
.el-icon {
position: relative;
z-index: 1;
}
}
.header-text {
.hub-title {
font-size: 24px;
font-weight: 700;
background: linear-gradient(135deg, #2d3748 0%, #667eea 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 4px;
}
.hub-subtitle {
font-size: 14px;
color: #64748b;
margin: 0;
}
}
}
.header-actions {
.refresh-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
border-radius: 12px;
padding: 12px 20px;
font-weight: 600;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
}
}
}
}
// 推荐统计样式
.recommendation-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
margin-bottom: 32px;
.stat-card {
background: linear-gradient(135deg, rgba(255,255,255,0.9) 0%, rgba(248,250,252,0.8) 100%);
border-radius: 16px;
padding: 24px;
display: flex;
align-items: center;
gap: 16px;
border: 1px solid rgba(255,255,255,0.3);
backdrop-filter: blur(10px);
transition: all 0.3s ease;
&:hover {
transform: translateY(-4px);
box-shadow: 0 12px 24px rgba(0,0,0,0.1);
}
.stat-icon {
width: 48px;
height: 48px;
border-radius: 12px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 20px;
}
.stat-content {
.stat-number {
font-size: 24px;
font-weight: 700;
color: #2d3748;
line-height: 1;
margin-bottom: 4px;
}
.stat-label {
font-size: 12px;
color: #64748b;
font-weight: 500;
}
}
}
}
// 推荐课程区域样式
.recommendations-section {
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
.section-title {
font-size: 20px;
font-weight: 600;
color: #2d3748;
margin: 0;
}
.filter-tabs {
.el-radio-group {
background: rgba(255,255,255,0.8);
border-radius: 12px;
padding: 4px;
backdrop-filter: blur(10px);
:deep(.el-radio-button__inner) {
border: none;
background: transparent;
border-radius: 8px;
padding: 8px 16px;
font-size: 12px;
font-weight: 500;
transition: all 0.3s ease;
}
:deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
}
}
}
}
}
// 课程网格样式
.course-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 24px;
.smart-course-card {
background: linear-gradient(135deg, rgba(255,255,255,0.95) 0%, rgba(248,250,252,0.9) 100%);
border-radius: 16px;
padding: 16px;
border: 1px solid rgba(255,255,255,0.3);
backdrop-filter: blur(15px);
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
cursor: pointer;
position: relative;
overflow: hidden;
animation: slideUp 0.6s ease-out both;
animation-delay: var(--delay);
&:before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(102,126,234,0.05) 0%, rgba(118,75,162,0.03) 100%);
opacity: 0;
transition: opacity 0.3s ease;
}
&:hover {
transform: translateY(-8px) scale(1.02);
box-shadow: 0 20px 40px rgba(0,0,0,0.15);
border-color: rgba(102, 126, 234, 0.3);
&:before {
opacity: 1;
}
}
&.featured {
border: 2px solid rgba(102, 126, 234, 0.3);
box-shadow: 0 8px 32px rgba(102, 126, 234, 0.15);
}
}
// 智能课程卡片内部样式
.smart-course-card {
// 卡片装饰
.card-decoration {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
z-index: 2;
.decoration-line {
height: 100%;
background: linear-gradient(90deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
border-radius: 20px 20px 0 0;
}
.priority-indicator {
position: absolute;
top: -2px;
right: 20px;
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 12px;
&.high {
background: linear-gradient(135deg, #f56565 0%, #e53e3e 100%);
box-shadow: 0 4px 12px rgba(245, 101, 101, 0.4);
}
&.medium {
background: linear-gradient(135deg, #ed8936 0%, #dd6b20 100%);
box-shadow: 0 4px 12px rgba(237, 137, 54, 0.4);
}
&.low {
background: linear-gradient(135deg, #a0aec0 0%, #718096 100%);
box-shadow: 0 4px 12px rgba(160, 174, 192, 0.4);
}
}
}
// 智能封面
.smart-cover {
position: relative;
height: 80px;
margin: -16px -16px 16px -16px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 70%, #f093fb 100%);
overflow: hidden;
border-radius: 16px 16px 0 0;
.cover-gradient {
position: absolute;
inset: 0;
background:
radial-gradient(circle at 30% 20%, rgba(255,255,255,0.3) 0%, transparent 50%),
radial-gradient(circle at 80% 80%, rgba(255,255,255,0.2) 0%, transparent 50%);
}
.cover-content {
position: relative;
z-index: 1;
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
.course-icon {
color: rgba(255,255,255,0.9);
filter: drop-shadow(0 2px 8px rgba(0,0,0,0.2));
.el-icon {
font-size: 28px;
}
}
.match-score {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
.score-ring {
position: relative;
width: 36px;
height: 36px;
.score-circle {
width: 100%;
height: 100%;
.circle-bg {
fill: none;
stroke: rgba(255,255,255,0.3);
stroke-width: 2;
}
.circle {
fill: none;
stroke: #fff;
stroke-width: 2;
stroke-linecap: round;
transform: rotate(-90deg);
transform-origin: 50% 50%;
transition: stroke-dasharray 0.6s ease;
}
}
.score-text {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 700;
color: white;
}
}
.score-label {
font-size: 10px;
color: rgba(255,255,255,0.8);
font-weight: 500;
}
}
}
}
// 智能信息区域
.smart-info {
.course-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 8px;
.course-title {
font-size: 18px;
font-weight: 700;
color: #2d3748;
line-height: 1.4;
flex: 1;
margin-right: 12px;
}
.priority-tag {
font-size: 10px;
font-weight: 600;
padding: 4px 8px;
border-radius: 12px;
white-space: nowrap;
&.high {
background: linear-gradient(135deg, rgba(245, 101, 101, 0.15) 0%, rgba(229, 62, 62, 0.1) 100%);
color: #e53e3e;
border: 1px solid rgba(245, 101, 101, 0.2);
}
&.medium {
background: linear-gradient(135deg, rgba(237, 137, 54, 0.15) 0%, rgba(221, 107, 32, 0.1) 100%);
color: #dd6b20;
border: 1px solid rgba(237, 137, 54, 0.2);
}
&.low {
background: linear-gradient(135deg, rgba(160, 174, 192, 0.15) 0%, rgba(113, 128, 150, 0.1) 100%);
color: #718096;
border: 1px solid rgba(160, 174, 192, 0.2);
}
}
}
.course-description {
font-size: 13px;
color: #64748b;
line-height: 1.5;
margin-bottom: 12px;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
// AI分析结果
.ai-analysis {
background: linear-gradient(135deg, rgba(102, 126, 234, 0.08) 0%, rgba(118, 75, 162, 0.05) 100%);
border-radius: 8px;
padding: 12px;
margin-bottom: 12px;
border: 1px solid rgba(102, 126, 234, 0.15);
position: relative;
&:before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 0 2px 2px 0;
}
.analysis-header {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
font-weight: 600;
color: #667eea;
margin-bottom: 12px;
}
.analysis-content {
.weakness-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-bottom: 10px;
.el-tag {
font-size: 11px;
border-radius: 6px;
.el-icon {
font-size: 10px;
margin-right: 4px;
}
}
}
.improvement-badge {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: #10b981;
font-weight: 600;
.el-icon {
font-size: 14px;
}
}
}
}
// 课程元数据
.course-metadata {
display: flex;
justify-content: space-between;
margin-bottom: 12px;
.meta-item {
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
color: #64748b;
.el-icon {
font-size: 14px;
color: #94a3b8;
}
}
}
// 学习进度
.progress-section {
margin-bottom: 12px;
.progress-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
.progress-label {
font-size: 12px;
color: #64748b;
font-weight: 500;
}
.progress-value {
font-size: 12px;
color: #667eea;
font-weight: 600;
}
}
}
// 智能操作按钮
.smart-actions {
display: flex;
flex-direction: column;
gap: 12px;
.primary-action {
width: 100%;
height: 44px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
border-radius: 22px;
font-size: 14px;
font-weight: 600;
color: white;
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%);
}
.el-icon {
margin-right: 8px;
font-size: 16px;
}
}
.action-grid {
display: grid !important;
grid-template-columns: repeat(2, 1fr) !important;
grid-template-rows: repeat(2, 38px) !important;
gap: 10px !important;
align-items: stretch !important;
justify-items: stretch !important;
width: 100% !important;
// 重置所有可能的Element Plus样式
:deep(.el-button) {
width: 100% !important;
height: 38px !important;
min-height: 38px !important;
max-height: 38px !important;
margin: 0 !important;
padding: 0 16px !important;
border-radius: 19px !important;
font-size: 12px !important;
line-height: 1 !important;
box-sizing: border-box !important;
}
.action-btn.el-button {
width: 100% !important;
height: 38px !important;
min-height: 38px !important;
max-height: 38px !important;
border: 1px solid #e2e8f0 !important;
border-radius: 19px !important;
font-size: 12px !important;
font-weight: 500 !important;
line-height: 1 !important;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
background: #ffffff !important;
color: #64748b !important;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1) !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
position: relative !important;
overflow: hidden !important;
cursor: pointer !important;
padding: 0 16px !important;
margin: 0 !important;
// 添加涟漪效果
&:before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
background: rgba(102, 126, 234, 0.2);
transform: translate(-50%, -50%);
transition: all 0.4s ease;
pointer-events: none;
}
&:active:before {
width: 100px;
height: 100px;
transition: all 0.2s ease;
}
&:hover {
transform: translateY(-2px) scale(1.02) !important;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15) !important;
background: #f8fafc !important;
border-color: #cbd5e1 !important;
}
&:active {
transform: translateY(0) scale(0.98) !important;
transition: transform 0.1s ease !important;
}
.el-icon {
margin-right: 6px !important;
font-size: 14px !important;
color: #94a3b8 !important;
position: relative !important;
z-index: 2 !important;
flex-shrink: 0 !important;
}
// 确保文字不换行
span {
position: relative !important;
z-index: 2 !important;
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
flex: 1 !important;
text-align: center !important;
}
// 考试按钮特殊样式
&.exam-btn {
background: #fffbeb !important;
color: #92400e !important;
border: 1px solid #fbbf24 !important;
height: 38px !important;
min-height: 38px !important;
max-height: 38px !important;
&:before {
background: rgba(251, 191, 36, 0.2) !important;
}
&:hover {
background: #fef3cd !important;
border-color: #f59e0b !important;
box-shadow: 0 6px 20px rgba(251, 191, 36, 0.2) !important;
transform: translateY(-2px) scale(1.02) !important;
}
.el-icon {
color: #d97706 !important;
margin-right: 6px !important;
font-size: 14px !important;
}
}
// 陪练按钮特殊样式
&.practice-btn {
background: #f0fdf4 !important;
color: #065f46 !important;
border: 1px solid #34d399 !important;
height: 38px !important;
min-height: 38px !important;
max-height: 38px !important;
&:before {
background: rgba(52, 211, 153, 0.2) !important;
}
&:hover {
background: #dcfce7 !important;
border-color: #10b981 !important;
box-shadow: 0 6px 20px rgba(52, 211, 153, 0.2) !important;
transform: translateY(-2px) scale(1.02) !important;
}
.el-icon {
color: #059669 !important;
margin-right: 6px !important;
font-size: 14px !important;
}
}
}
}
}
}
}
}
// 空状态样式
.empty-recommendations {
text-align: center;
padding: 60px 20px;
.empty-icon {
margin-bottom: 24px;
color: #94a3b8;
.rotating {
animation: rotate 2s linear infinite;
}
}
.empty-title {
font-size: 20px;
font-weight: 600;
color: #475569;
margin-bottom: 12px;
}
.empty-description {
color: #64748b;
margin-bottom: 32px;
}
}
}
// 动画定义
@keyframes pulse {
0%, 100% { opacity: 0.3; }
50% { opacity: 0.6; }
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
}
// 响应式设计
@media (max-width: 768px) {
.growth-path-container {
.personal-info {
flex-direction: column;
gap: 20px;
.info-left {
flex-direction: column;
text-align: center;
.info-content {
.el-progress {
width: 100%;
}
}
}
}
.main-content {
grid-template-columns: 1fr;
}
.tree-container {
.level-nodes {
grid-template-columns: 1fr !important;
}
}
.ai-learning-hub-inner {
padding: 20px;
.hub-header {
flex-direction: column;
gap: 16px;
text-align: center;
.header-left {
flex-direction: column;
gap: 12px;
}
}
.recommendation-stats {
grid-template-columns: 1fr;
gap: 16px;
}
.course-grid {
grid-template-columns: 1fr;
gap: 20px;
}
.recommendations-section {
.section-header {
flex-direction: column;
gap: 16px;
text-align: center;
}
}
}
}
}
// 手机端深度优化
@media (max-width: 480px) {
.growth-path-container {
padding: 12px;
.personal-info {
padding: 16px;
border-radius: 12px;
.info-left {
.el-avatar {
width: 64px !important;
height: 64px !important;
}
.info-content {
.user-name {
font-size: 18px;
}
.user-meta {
font-size: 12px;
gap: 6px;
.separator {
margin: 0 4px;
}
}
}
}
.info-right {
.el-button {
width: 100%;
}
}
}
.main-content {
gap: 16px;
margin-top: 16px;
.card {
padding: 14px;
border-radius: 12px;
.card-header {
flex-direction: column;
gap: 12px;
margin-bottom: 16px;
.card-title {
font-size: 16px;
}
.el-button {
width: 100%;
}
}
}
.ability-radar {
.radar-chart {
height: 260px;
}
.ability-feedback {
.feedback-item {
padding: 12px;
.feedback-header-row {
.dimension-name {
font-size: 14px;
}
.dimension-score {
font-size: 13px;
}
}
.feedback-text {
font-size: 12px;
line-height: 1.6;
}
}
}
}
}
.ai-learning-hub-inner {
padding: 16px;
border-radius: 12px;
.hub-header {
.ai-avatar {
width: 48px;
height: 48px;
}
.header-text {
.hub-title {
font-size: 17px;
}
.hub-subtitle {
font-size: 12px;
}
}
.header-actions {
width: 100%;
.refresh-btn {
width: 100%;
}
}
}
.recommendation-stats {
.stat-card {
padding: 12px;
.stat-content {
.stat-number {
font-size: 20px;
}
.stat-label {
font-size: 11px;
}
}
}
}
.recommendations-section {
.section-header {
.section-title {
font-size: 15px;
}
.filter-tabs {
width: 100%;
overflow-x: auto;
.el-radio-group {
flex-wrap: nowrap;
:deep(.el-radio-button__inner) {
padding: 6px 12px;
font-size: 12px;
}
}
}
}
.course-grid {
gap: 14px;
.smart-course-card {
padding: 14px;
border-radius: 12px;
.card-content {
.course-header {
.course-name {
font-size: 15px;
}
}
.course-reason {
font-size: 12px;
}
.course-meta {
gap: 6px;
.meta-tag {
font-size: 11px;
padding: 3px 8px;
}
}
.action-row {
flex-direction: column;
gap: 10px;
.improvement-badge,
.el-button {
width: 100%;
}
}
}
}
}
}
}
}
}
</style>