- 新增数据库表: growth_path_nodes, user_growth_path_progress, user_node_completions - 新增 Model: GrowthPathNode, UserGrowthPathProgress, UserNodeCompletion - 新增 Service: GrowthPathService(管理端CRUD、学员端进度追踪) - 新增 API: 学员端获取成长路径、管理端CRUD - 前端学员端从API动态加载成长路径数据 - 更新管理端API接口定义
This commit is contained in:
@@ -102,28 +102,97 @@ export interface CourseDetailForManager extends CourseInfo {
|
||||
}>
|
||||
}
|
||||
|
||||
// 成长路径配置
|
||||
export interface GrowthPathConfig {
|
||||
// 成长路径节点
|
||||
export interface GrowthPathNode {
|
||||
id: number
|
||||
positionId: number
|
||||
positionName: string
|
||||
growth_path_id: number
|
||||
course_id: number
|
||||
course_name?: string
|
||||
stage_name?: string
|
||||
title: string
|
||||
description?: string
|
||||
order_num: number
|
||||
is_required: boolean
|
||||
prerequisites?: number[]
|
||||
estimated_days: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
// 阶段配置
|
||||
export interface StageConfig {
|
||||
name: string
|
||||
description?: string
|
||||
courses: Array<{
|
||||
courseId: number
|
||||
courseName: string
|
||||
isRequired: boolean
|
||||
prerequisites: number[]
|
||||
order: number
|
||||
estimatedDays: number
|
||||
}>
|
||||
totalCourses: number
|
||||
requiredCourses: number
|
||||
optionalCourses: number
|
||||
estimatedDays: number
|
||||
status: 'active' | 'inactive'
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
order: number
|
||||
}
|
||||
|
||||
// 成长路径配置(新版)
|
||||
export interface GrowthPathConfig {
|
||||
id: number
|
||||
name: string
|
||||
description?: string
|
||||
target_role?: string
|
||||
position_id?: number
|
||||
position_name?: string
|
||||
stages?: StageConfig[]
|
||||
estimated_duration_days?: number
|
||||
is_active: boolean
|
||||
sort_order: number
|
||||
nodes: GrowthPathNode[]
|
||||
node_count: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
// 成长路径列表项
|
||||
export interface GrowthPathListItem {
|
||||
id: number
|
||||
name: string
|
||||
description?: string
|
||||
position_id?: number
|
||||
position_name?: string
|
||||
is_active: boolean
|
||||
node_count: number
|
||||
estimated_duration_days?: number
|
||||
created_at: string
|
||||
}
|
||||
|
||||
// 创建节点请求
|
||||
export interface CreateGrowthPathNode {
|
||||
course_id: number
|
||||
stage_name?: string
|
||||
title: string
|
||||
description?: string
|
||||
order_num: number
|
||||
is_required: boolean
|
||||
prerequisites?: number[]
|
||||
estimated_days: number
|
||||
}
|
||||
|
||||
// 创建成长路径请求
|
||||
export interface CreateGrowthPathRequest {
|
||||
name: string
|
||||
description?: string
|
||||
target_role?: string
|
||||
position_id?: number
|
||||
stages?: StageConfig[]
|
||||
estimated_duration_days?: number
|
||||
is_active?: boolean
|
||||
sort_order?: number
|
||||
nodes?: CreateGrowthPathNode[]
|
||||
}
|
||||
|
||||
// 更新成长路径请求
|
||||
export interface UpdateGrowthPathRequest {
|
||||
name?: string
|
||||
description?: string
|
||||
target_role?: string
|
||||
position_id?: number
|
||||
stages?: StageConfig[]
|
||||
estimated_duration_days?: number
|
||||
is_active?: boolean
|
||||
sort_order?: number
|
||||
nodes?: CreateGrowthPathNode[]
|
||||
}
|
||||
|
||||
// AI陪练场景(管理视图)
|
||||
@@ -364,49 +433,44 @@ export const analyzeKnowledgePoints = (courseId: number) => {
|
||||
*/
|
||||
export const getGrowthPathConfigs = (params: {
|
||||
page?: number
|
||||
size?: number
|
||||
positionId?: number
|
||||
status?: string
|
||||
page_size?: number
|
||||
position_id?: number
|
||||
is_active?: boolean
|
||||
} = {}) => {
|
||||
return request.get<{
|
||||
items: GrowthPathConfig[]
|
||||
items: GrowthPathListItem[]
|
||||
total: number
|
||||
page: number
|
||||
size: number
|
||||
page_size: number
|
||||
}>('/api/v1/manager/growth-paths', { params })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取成长路径详情
|
||||
*/
|
||||
export const getGrowthPathDetail = (pathId: number) => {
|
||||
return request.get<GrowthPathConfig>(`/api/v1/manager/growth-paths/${pathId}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建成长路径
|
||||
*/
|
||||
export const createGrowthPath = (data: {
|
||||
positionId: number
|
||||
name: string
|
||||
description?: string
|
||||
courses: Array<{
|
||||
courseId: number
|
||||
isRequired: boolean
|
||||
prerequisites?: number[]
|
||||
estimatedDays?: number
|
||||
}>
|
||||
}) => {
|
||||
return request.post<GrowthPathConfig>('/api/v1/manager/growth-paths', data)
|
||||
export const createGrowthPath = (data: CreateGrowthPathRequest) => {
|
||||
return request.post<{ success: boolean; message: string; id: number }>('/api/v1/manager/growth-paths', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新成长路径
|
||||
*/
|
||||
export const updateGrowthPath = (pathId: number, data: {
|
||||
name?: string
|
||||
description?: string
|
||||
courses?: Array<{
|
||||
courseId: number
|
||||
isRequired: boolean
|
||||
prerequisites?: number[]
|
||||
estimatedDays?: number
|
||||
}>
|
||||
}) => {
|
||||
return request.put<GrowthPathConfig>(`/api/v1/manager/growth-paths/${pathId}`, data)
|
||||
export const updateGrowthPath = (pathId: number, data: UpdateGrowthPathRequest) => {
|
||||
return request.put<{ success: boolean; message: string }>(`/api/v1/manager/growth-paths/${pathId}`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除成长路径
|
||||
*/
|
||||
export const deleteGrowthPath = (pathId: number) => {
|
||||
return request.delete<{ success: boolean; message: string }>(`/api/v1/manager/growth-paths/${pathId}`)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -40,7 +40,48 @@ export interface CourseMaterial {
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
// 成长路径节点
|
||||
// 成长路径节点(学员端)
|
||||
export interface TraineeGrowthPathNode {
|
||||
id: number
|
||||
course_id: number
|
||||
title: string
|
||||
description?: string
|
||||
stage_name?: string
|
||||
is_required: boolean
|
||||
estimated_days: number
|
||||
order_num: number
|
||||
status: 'locked' | 'unlocked' | 'in_progress' | 'completed'
|
||||
progress: number
|
||||
course_name?: string
|
||||
course_cover?: string
|
||||
}
|
||||
|
||||
// 成长路径阶段
|
||||
export interface TraineeGrowthPathStage {
|
||||
name: string
|
||||
description?: string
|
||||
completed: number
|
||||
total: number
|
||||
nodes: TraineeGrowthPathNode[]
|
||||
}
|
||||
|
||||
// 成长路径(学员端响应)
|
||||
export interface TraineeGrowthPath {
|
||||
id: number
|
||||
name: string
|
||||
description?: string
|
||||
position_id?: number
|
||||
position_name?: string
|
||||
total_progress: number
|
||||
completed_nodes: number
|
||||
total_nodes: number
|
||||
status: 'not_started' | 'in_progress' | 'completed'
|
||||
started_at?: string
|
||||
estimated_completion_days?: number
|
||||
stages: TraineeGrowthPathStage[]
|
||||
}
|
||||
|
||||
// 兼容旧接口
|
||||
export interface GrowthPathNode {
|
||||
id: number
|
||||
courseId: number
|
||||
@@ -54,7 +95,7 @@ export interface GrowthPathNode {
|
||||
order: number
|
||||
}
|
||||
|
||||
// 成长路径
|
||||
// 兼容旧接口
|
||||
export interface GrowthPath {
|
||||
id: number
|
||||
name: string
|
||||
@@ -226,10 +267,35 @@ export const markMaterialCompleted = (materialId: number) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取成长路径
|
||||
* 获取成长路径(学员端)
|
||||
*/
|
||||
export const getGrowthPath = () => {
|
||||
return request.get<GrowthPath>('/api/v1/trainee/growth-path')
|
||||
export const getGrowthPath = (positionId?: number) => {
|
||||
return request.get<TraineeGrowthPath>('/api/v1/trainee/growth-path', {
|
||||
params: positionId ? { position_id: positionId } : {}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始学习成长路径
|
||||
*/
|
||||
export const startGrowthPath = (growthPathId: number) => {
|
||||
return request.post<{ success: boolean; message: string; progress_id: number }>('/api/v1/trainee/growth-path/start', {
|
||||
growth_path_id: growthPathId
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 完成成长路径节点
|
||||
*/
|
||||
export const completeGrowthPathNode = (nodeId: number) => {
|
||||
return request.post<{
|
||||
completed: boolean
|
||||
total_progress: number
|
||||
is_path_completed: boolean
|
||||
unlocked_nodes: number[]
|
||||
}>('/api/v1/trainee/growth-path/node/complete', {
|
||||
node_id: nodeId
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -363,7 +363,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import { ref, computed, onMounted, onUnmounted, nextTick, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {
|
||||
@@ -374,7 +374,8 @@ import {
|
||||
Cpu // 机器人图标
|
||||
} from '@element-plus/icons-vue'
|
||||
import * as echarts from 'echarts'
|
||||
import { analyzeYanjiBadge, getCourseDetail } from '@/api/trainee'
|
||||
import { analyzeYanjiBadge, getCourseDetail, getGrowthPath, startGrowthPath } from '@/api/trainee'
|
||||
import type { TraineeGrowthPath, TraineeGrowthPathStage } from '@/api/trainee'
|
||||
import { getCurrentUserProfile } from '@/api/user'
|
||||
|
||||
const router = useRouter()
|
||||
@@ -451,100 +452,73 @@ const expPercentage = computed(() => {
|
||||
return Math.round((userInfo.value.exp / userInfo.value.nextLevelExp) * 100)
|
||||
})
|
||||
|
||||
// 成长路径数据
|
||||
const growthPath = ref([
|
||||
{
|
||||
name: '基础阶段',
|
||||
completed: 3,
|
||||
total: 3,
|
||||
nodes: [
|
||||
{
|
||||
id: 1,
|
||||
title: '机构文化与服务理念',
|
||||
description: '了解机构的服务理念和专业文化',
|
||||
status: 'completed',
|
||||
progress: 100
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '美容产品基础知识',
|
||||
description: '学习美容产品的基本知识和使用方法',
|
||||
status: 'completed',
|
||||
progress: 100
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '客户接待与服务标准',
|
||||
description: '掌握专业的客户接待和服务流程',
|
||||
status: 'completed',
|
||||
progress: 100
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '进阶阶段',
|
||||
completed: 2,
|
||||
total: 4,
|
||||
nodes: [
|
||||
{
|
||||
id: 4,
|
||||
title: '皮肤分析技术',
|
||||
description: '学习专业的皮肤分析方法和技术',
|
||||
status: 'completed',
|
||||
progress: 100
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: '美容仪器操作',
|
||||
description: '掌握各种美容仪器的操作方法',
|
||||
status: 'completed',
|
||||
progress: 100
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: '客户沟通进阶',
|
||||
description: '学习高级的客户沟通和咨询技巧',
|
||||
status: 'current',
|
||||
progress: 65
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
title: '美容方案设计',
|
||||
description: '学习为客户设计个性化的美容方案',
|
||||
status: 'locked',
|
||||
progress: 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '高级阶段',
|
||||
completed: 0,
|
||||
total: 3,
|
||||
nodes: [
|
||||
{
|
||||
id: 8,
|
||||
title: '轻医美项目咨询',
|
||||
description: '掌握轻医美项目的专业咨询技能',
|
||||
status: 'locked',
|
||||
progress: 0
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
title: '团队协作与管理',
|
||||
description: '学习团队协作和基础管理技能',
|
||||
status: 'locked',
|
||||
progress: 0
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
title: '高级美容技术',
|
||||
description: '掌握高级美容技术和创新方法',
|
||||
status: 'locked',
|
||||
progress: 0
|
||||
}
|
||||
]
|
||||
// 成长路径数据(从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 || '开始学习失败')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化雷达图
|
||||
@@ -754,12 +728,12 @@ const handleNodeClick = (node: any) => {
|
||||
return
|
||||
}
|
||||
|
||||
if (node.status === 'completed') {
|
||||
ElMessage.info(`${node.title} 已完成`)
|
||||
return
|
||||
// 跳转到课程详情页
|
||||
if (node.courseId) {
|
||||
router.push(`/trainee/course-detail?id=${node.courseId}`)
|
||||
} else {
|
||||
ElMessage.info(`${node.title}`)
|
||||
}
|
||||
|
||||
ElMessage.success(`开始学习:${node.title}`)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -921,6 +895,9 @@ onMounted(async () => {
|
||||
// 获取用户信息
|
||||
await fetchUserInfo()
|
||||
|
||||
// 加载成长路径数据
|
||||
await loadGrowthPath()
|
||||
|
||||
nextTick(() => {
|
||||
initRadarChart()
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user