feat:对接环境配置,登录,课程相关接口对接
This commit is contained in:
parent
a428d2b36b
commit
13113e1bbc
2
.env
2
.env
@ -1,5 +1,5 @@
|
||||
# API配置
|
||||
VITE_API_BASE_URL=http://110.42.96.65:55510/api
|
||||
VITE_API_BASE_URL=http://103.40.14.23:25526/jeecgboot
|
||||
|
||||
# Mock配置 - 禁用Mock,使用真实API
|
||||
VITE_ENABLE_MOCK=false
|
||||
|
@ -1,7 +1,7 @@
|
||||
# 开发环境配置
|
||||
|
||||
# API配置
|
||||
VITE_API_BASE_URL=http://110.42.96.65:55510/api
|
||||
VITE_API_BASE_URL=http://103.40.14.23:25526/jeecgboot
|
||||
|
||||
# Mock配置
|
||||
# 设置为 true 使用Mock数据,false 使用真实API
|
||||
|
@ -1,7 +1,7 @@
|
||||
# 生产环境配置
|
||||
|
||||
# API配置
|
||||
VITE_API_BASE_URL=http://110.42.96.65:55510/api
|
||||
VITE_API_BASE_URL=http://103.40.14.23:25526/jeecgboot
|
||||
|
||||
# Mock配置 - 生产环境禁用Mock,使用真实API
|
||||
VITE_ENABLE_MOCK=false
|
||||
|
@ -12,13 +12,13 @@ export { default as UploadApi } from './modules/upload'
|
||||
export { default as StatisticsApi } from './modules/statistics'
|
||||
|
||||
// API 基础配置
|
||||
export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/api'
|
||||
export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/jeecgboot'
|
||||
|
||||
// API 端点配置
|
||||
export const API_ENDPOINTS = {
|
||||
// 认证相关
|
||||
AUTH: {
|
||||
LOGIN: '/auth/login',
|
||||
LOGIN: '/biz/user/login',
|
||||
REGISTER: '/auth/register',
|
||||
LOGOUT: '/auth/logout',
|
||||
REFRESH: '/auth/refresh',
|
||||
@ -34,12 +34,12 @@ export const API_ENDPOINTS = {
|
||||
|
||||
// 课程相关
|
||||
COURSES: {
|
||||
LIST: '/courses',
|
||||
LIST: '/biz/course/list',
|
||||
SEARCH: '/courses/search',
|
||||
POPULAR: '/courses/popular',
|
||||
LATEST: '/courses/latest',
|
||||
RECOMMENDED: '/courses/recommended',
|
||||
DETAIL: '/courses/:id',
|
||||
DETAIL: '/biz/course/detail',
|
||||
CHAPTERS: '/courses/:id/chapters',
|
||||
LESSONS: '/courses/:id/lessons',
|
||||
ENROLL: '/courses/:id/enroll',
|
||||
@ -52,10 +52,20 @@ export const API_ENDPOINTS = {
|
||||
|
||||
// 分类相关
|
||||
CATEGORIES: {
|
||||
LIST: '/categories',
|
||||
LIST: '/biz/course/category/list',
|
||||
COURSES: '/categories/:id/courses',
|
||||
},
|
||||
|
||||
// 专题相关
|
||||
SUBJECTS: {
|
||||
LIST: '/biz/course/subject/list',
|
||||
},
|
||||
|
||||
// 难度相关
|
||||
DIFFICULTIES: {
|
||||
LIST: '/biz/course/difficulty/list',
|
||||
},
|
||||
|
||||
// 章节课时相关
|
||||
CHAPTERS: {
|
||||
DETAIL: '/chapters/:id',
|
||||
|
@ -17,10 +17,16 @@ export class AuthApi {
|
||||
// 用户登录
|
||||
static async login(data: LoginRequest): Promise<ApiResponse<LoginResponse>> {
|
||||
try {
|
||||
console.log('🚀 发送登录请求:', { url: '/users/login', data: { ...data, password: '***' } })
|
||||
// 转换参数格式:将email/phone转换为username
|
||||
const loginData = {
|
||||
username: data.email || data.phone || '',
|
||||
password: data.password
|
||||
}
|
||||
|
||||
console.log('🚀 发送登录请求:', { url: '/biz/user/login', data: { ...loginData, password: '***' } })
|
||||
|
||||
// 调用后端API
|
||||
const response = await ApiRequest.post<any>('/users/login', data)
|
||||
const response = await ApiRequest.post<any>('/biz/user/login', loginData)
|
||||
|
||||
console.log('🔍 Login API Response:', response)
|
||||
console.log('🔍 Response Code:', response.code)
|
||||
@ -34,12 +40,19 @@ export class AuthApi {
|
||||
|
||||
// 如果response.code是undefined,检查response.data是否包含完整的API响应
|
||||
if (actualCode === undefined && actualData && typeof actualData === 'object') {
|
||||
if ('code' in actualData && 'message' in actualData && 'data' in actualData) {
|
||||
// 这种情况下,真正的API响应被包装在了response.data中
|
||||
// 检查是否是标准的jeecg-boot响应格式 (success, code, message, result)
|
||||
if ('success' in actualData && 'code' in actualData && 'message' in actualData && 'result' in actualData) {
|
||||
actualCode = actualData.code
|
||||
actualMessage = actualData.message
|
||||
actualData = actualData.result
|
||||
console.log('🔧 修正后的响应 (jeecg-boot格式):', { code: actualCode, message: actualMessage, data: actualData })
|
||||
}
|
||||
// 检查是否是其他格式 (code, message, data)
|
||||
else if ('code' in actualData && 'message' in actualData && 'data' in actualData) {
|
||||
actualCode = actualData.code
|
||||
actualMessage = actualData.message
|
||||
actualData = actualData.data
|
||||
console.log('🔧 修正后的响应:', { code: actualCode, message: actualMessage, data: actualData })
|
||||
console.log('🔧 修正后的响应 (标准格式):', { code: actualCode, message: actualMessage, data: actualData })
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,8 +67,8 @@ export class AuthApi {
|
||||
} as ApiResponse<LoginResponse>
|
||||
}
|
||||
|
||||
// 如果后端返回的是真实API格式(包含token, timestamp, expires)
|
||||
if (actualData && actualData.token && actualData.timestamp) {
|
||||
// 如果后端返回的是jeecg-boot格式(只包含token)
|
||||
if (actualData && actualData.token) {
|
||||
const adaptedResponse: ApiResponse<LoginResponse> = {
|
||||
code: actualCode,
|
||||
message: actualMessage || '登录成功',
|
||||
@ -64,7 +77,7 @@ export class AuthApi {
|
||||
id: 1, // 真实API没有返回用户ID,使用默认值
|
||||
email: data.email || '',
|
||||
phone: data.phone || '',
|
||||
username: data.phone || data.email?.split('@')[0] || 'user',
|
||||
username: data.email || data.phone || '',
|
||||
nickname: '用户',
|
||||
avatar: '',
|
||||
role: 'student',
|
||||
@ -216,7 +229,7 @@ export class AuthApi {
|
||||
|
||||
// 获取当前用户信息
|
||||
static getCurrentUser(): Promise<ApiResponse<User>> {
|
||||
return ApiRequest.get('/auth/me')
|
||||
return ApiRequest.get('/users/info')
|
||||
}
|
||||
|
||||
// 更新用户资料
|
||||
|
@ -5,6 +5,19 @@ import type {
|
||||
PaginationResponse,
|
||||
Course,
|
||||
CourseCategory,
|
||||
BackendCourseCategory,
|
||||
BackendCourseCategoryListResponse,
|
||||
CourseSubject,
|
||||
BackendCourseSubject,
|
||||
BackendCourseSubjectListResponse,
|
||||
CourseDifficulty,
|
||||
BackendCourseDifficulty,
|
||||
BackendCourseDifficultyListResponse,
|
||||
CourseListQueryParams,
|
||||
CourseDetailQueryParams,
|
||||
BackendCourseItem,
|
||||
BackendCourseListResponse,
|
||||
BackendCourseDetailResponse,
|
||||
Chapter,
|
||||
Lesson,
|
||||
LessonResource,
|
||||
@ -17,7 +30,6 @@ import type {
|
||||
SearchRequest,
|
||||
Instructor,
|
||||
BackendCourse,
|
||||
BackendCourseListResponse,
|
||||
CourseListRequest,
|
||||
} from '../types'
|
||||
|
||||
@ -76,128 +88,96 @@ export class CourseApi {
|
||||
return '待定'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 映射后端难度值到前端级别
|
||||
*/
|
||||
private static mapDifficultyToLevel(difficulty: number): string {
|
||||
switch (difficulty) {
|
||||
case 0: return '零基础'
|
||||
case 1: return '初级'
|
||||
case 2: return '进阶'
|
||||
case 3: return '高阶'
|
||||
default: return '未知'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 映射后端难度值到标准级别
|
||||
*/
|
||||
private static mapDifficultyToStandardLevel(difficulty: number): 'beginner' | 'intermediate' | 'advanced' {
|
||||
switch (difficulty) {
|
||||
case 0: return 'beginner'
|
||||
case 1: return 'beginner'
|
||||
case 2: return 'intermediate'
|
||||
case 3: return 'advanced'
|
||||
default: return 'beginner'
|
||||
}
|
||||
}
|
||||
// 获取课程列表 - 适配后端接口
|
||||
static async getCourses(params?: CourseListRequest): Promise<ApiResponse<PaginationResponse<Course>>> {
|
||||
static async getCourses(params?: CourseListQueryParams): Promise<ApiResponse<Course[]>> {
|
||||
try {
|
||||
console.log('调用课程列表API,参数:', params)
|
||||
console.log('🚀 调用课程列表API,参数:', params)
|
||||
|
||||
// 构建查询参数,根据API文档的参数名称
|
||||
const queryParams: any = {}
|
||||
if (params?.categoryId) queryParams.categoryId = params.categoryId
|
||||
if (params?.difficulty !== undefined) queryParams.difficulty = params.difficulty
|
||||
if (params?.difficulty) queryParams.difficulty = params.difficulty
|
||||
if (params?.subject) queryParams.subject = params.subject
|
||||
if (params?.page) queryParams.page = params.page
|
||||
if (params?.pageSize) queryParams.pageSize = params.pageSize
|
||||
if (params?.keyword) queryParams.keyword = params.keyword
|
||||
|
||||
console.log('🔍 查询参数:', queryParams)
|
||||
|
||||
// 调用后端API
|
||||
const response = await ApiRequest.get<BackendCourseListResponse>('/lesson/list', queryParams)
|
||||
console.log('课程列表API响应:', response)
|
||||
console.log('响应数据结构:', response.data)
|
||||
console.log('响应数据类型:', typeof response.data)
|
||||
const response = await ApiRequest.get<any>('/biz/course/list', queryParams)
|
||||
console.log('🔍 课程列表API响应:', response)
|
||||
|
||||
// 检查是否是axios响应格式还是我们的ApiResponse格式
|
||||
let actualData: any
|
||||
let actualCode: number
|
||||
let actualMessage: string
|
||||
|
||||
// 使用类型断言来处理不同的响应格式
|
||||
const responseAny = response as any
|
||||
|
||||
if (responseAny.data && typeof responseAny.data === 'object' && 'data' in responseAny.data) {
|
||||
// 这是我们期望的ApiResponse格式: { code, message, data: { list, total } }
|
||||
actualData = responseAny.data.data
|
||||
actualCode = responseAny.data.code
|
||||
actualMessage = responseAny.data.message
|
||||
console.log('检测到ApiResponse格式')
|
||||
} else {
|
||||
// 这可能是直接的axios响应格式: { list, total }
|
||||
actualData = responseAny.data
|
||||
actualCode = responseAny.status || 200
|
||||
actualMessage = responseAny.statusText || 'OK'
|
||||
console.log('检测到直接响应格式')
|
||||
}
|
||||
|
||||
console.log('实际数据:', actualData)
|
||||
console.log('实际数据的list字段:', actualData?.list)
|
||||
console.log('list是否为数组:', Array.isArray(actualData?.list))
|
||||
|
||||
// 检查响应数据的有效性
|
||||
if (!actualData || !Array.isArray(actualData.list)) {
|
||||
console.warn('API响应数据格式异常')
|
||||
console.warn('期望的格式: { list: [], total: number }')
|
||||
console.warn('实际收到的格式:', actualData)
|
||||
return {
|
||||
code: actualCode || 0,
|
||||
message: actualMessage || '数据格式异常',
|
||||
data: {
|
||||
list: [],
|
||||
total: 0,
|
||||
page: params?.page || 1,
|
||||
pageSize: params?.pageSize || 10,
|
||||
totalPages: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 适配后端响应格式为前端期望的格式
|
||||
const adaptedCourses: Course[] = actualData.list.map((backendCourse: BackendCourse) => ({
|
||||
id: backendCourse.id,
|
||||
title: backendCourse.name,
|
||||
description: backendCourse.description,
|
||||
thumbnail: backendCourse.cover,
|
||||
coverImage: backendCourse.cover,
|
||||
price: parseFloat(backendCourse.price || '0'),
|
||||
originalPrice: parseFloat(backendCourse.price || '0'),
|
||||
currency: 'CNY',
|
||||
rating: 4.5, // 默认评分,后端没有返回
|
||||
ratingCount: 0, // 默认评分数量
|
||||
studentsCount: 0, // 默认学生数量
|
||||
duration: '待定', // 默认时长
|
||||
totalLessons: 0, // 默认课程数量
|
||||
level: this.mapDifficulty(backendCourse.difficulty || 0),
|
||||
language: 'zh-CN',
|
||||
category: {
|
||||
id: backendCourse.categoryId,
|
||||
name: this.getCategoryName(backendCourse.categoryId),
|
||||
slug: 'category-' + backendCourse.categoryId
|
||||
},
|
||||
tags: backendCourse.subject ? [backendCourse.subject] : [],
|
||||
skills: [],
|
||||
requirements: backendCourse.prerequisite ? [backendCourse.prerequisite] : [],
|
||||
objectives: backendCourse.target ? [backendCourse.target] : [],
|
||||
instructor: {
|
||||
id: backendCourse.teacherId || 0,
|
||||
name: backendCourse.school || '未知讲师',
|
||||
title: '讲师',
|
||||
bio: '',
|
||||
avatar: '',
|
||||
rating: 4.5,
|
||||
studentsCount: 0,
|
||||
coursesCount: 0,
|
||||
experience: '',
|
||||
education: [],
|
||||
certifications: []
|
||||
},
|
||||
status: 'published' as const,
|
||||
createdAt: this.formatTimestamp(backendCourse.createdTime),
|
||||
updatedAt: this.formatTimestamp(backendCourse.updatedTime),
|
||||
publishedAt: this.formatTimestamp(backendCourse.createdTime)
|
||||
// 处理后端响应格式
|
||||
if (response.data && response.data.success && response.data.result) {
|
||||
// 转换后端数据格式为前端格式
|
||||
const courses: Course[] = response.data.result.map((item: BackendCourseItem) => ({
|
||||
id: item.id, // 保持字符串格式,不转换为数字
|
||||
title: item.name || '',
|
||||
description: item.description || '',
|
||||
instructor: item.school || '未知讲师',
|
||||
duration: item.arrangement || '待定',
|
||||
level: this.mapDifficultyToLevel(item.difficulty),
|
||||
category: item.subject || '其他',
|
||||
thumbnail: item.cover || '',
|
||||
price: 0, // 后端没有价格字段,设为0
|
||||
rating: 0, // 后端没有评分字段,设为0
|
||||
studentsCount: item.enrollCount || 0,
|
||||
lessonsCount: 0, // 后端没有课程数量字段,设为0
|
||||
tags: [],
|
||||
isEnrolled: false,
|
||||
progress: 0,
|
||||
createdAt: this.formatTimestamp(item.createTime),
|
||||
updatedAt: this.formatTimestamp(item.updateTime),
|
||||
status: item.status === 1 ? 'published' : 'draft',
|
||||
enrollmentCount: item.enrollCount || 0,
|
||||
maxEnrollment: item.maxEnroll || 0,
|
||||
startDate: item.startTime || '',
|
||||
endDate: item.endTime || '',
|
||||
outline: item.outline || '',
|
||||
prerequisite: item.prerequisite || '',
|
||||
reference: item.reference || '',
|
||||
target: item.target || '',
|
||||
question: item.question || '',
|
||||
video: item.video || ''
|
||||
}))
|
||||
|
||||
const adaptedResponse: ApiResponse<PaginationResponse<Course>> = {
|
||||
code: actualCode,
|
||||
message: actualMessage,
|
||||
data: {
|
||||
list: adaptedCourses,
|
||||
total: actualData.total || 0,
|
||||
page: params?.page || 1,
|
||||
pageSize: params?.pageSize || 10,
|
||||
totalPages: Math.ceil((actualData.total || 0) / (params?.pageSize || 10))
|
||||
return {
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: courses
|
||||
}
|
||||
} else {
|
||||
console.warn('⚠️ 课程列表API返回格式异常:', response)
|
||||
return {
|
||||
code: 500,
|
||||
message: response.data?.message || '获取课程列表失败',
|
||||
data: []
|
||||
}
|
||||
}
|
||||
|
||||
return adaptedResponse
|
||||
} catch (error: any) {
|
||||
console.error('课程API调用失败:', error)
|
||||
|
||||
@ -225,6 +205,95 @@ export class CourseApi {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取课程详情 - 适配后端接口
|
||||
static async getCourseDetail(id: string): Promise<ApiResponse<Course>> {
|
||||
try {
|
||||
console.log('🚀 调用课程详情API,课程ID:', id)
|
||||
|
||||
// 调用后端API
|
||||
const response = await ApiRequest.get<any>('/biz/course/detail', { id })
|
||||
|
||||
console.log('🔍 课程详情API响应:', response)
|
||||
|
||||
// 处理后端响应格式
|
||||
if (response.data && response.data.success) {
|
||||
// 检查result是否为null或空
|
||||
if (!response.data.result) {
|
||||
console.warn('⚠️ 课程详情为空,可能课程不存在或已删除')
|
||||
return {
|
||||
code: 404,
|
||||
message: '课程不存在或已删除',
|
||||
data: {} as Course
|
||||
}
|
||||
}
|
||||
// 转换后端数据格式为前端格式
|
||||
const item: BackendCourseItem = response.data.result
|
||||
const course: Course = {
|
||||
id: item.id, // 保持字符串格式,不转换为数字
|
||||
title: item.name || '',
|
||||
description: item.description || '',
|
||||
thumbnail: item.cover || '',
|
||||
price: 0, // 后端没有价格字段,设为0
|
||||
currency: 'CNY',
|
||||
rating: 0, // 后端没有评分字段,设为0
|
||||
ratingCount: 0,
|
||||
studentsCount: item.enrollCount || 0,
|
||||
duration: item.arrangement || '待定',
|
||||
totalLessons: 0,
|
||||
level: this.mapDifficultyToStandardLevel(item.difficulty),
|
||||
language: 'zh-CN',
|
||||
category: {
|
||||
id: 1,
|
||||
name: item.subject || '其他',
|
||||
slug: 'other'
|
||||
},
|
||||
tags: [],
|
||||
skills: [],
|
||||
requirements: item.prerequisite ? [item.prerequisite] : [],
|
||||
objectives: item.target ? [item.target] : [],
|
||||
instructor: {
|
||||
id: 1,
|
||||
name: item.school || '未知讲师',
|
||||
title: '讲师',
|
||||
bio: '',
|
||||
avatar: '',
|
||||
rating: 4.5,
|
||||
studentsCount: 0,
|
||||
coursesCount: 0,
|
||||
experience: '',
|
||||
education: [],
|
||||
certifications: []
|
||||
},
|
||||
status: item.status === 1 ? 'published' : 'draft',
|
||||
isEnrolled: false,
|
||||
progress: 0,
|
||||
createdAt: this.formatTimestamp(item.createTime),
|
||||
updatedAt: this.formatTimestamp(item.updateTime)
|
||||
}
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: course
|
||||
}
|
||||
} else {
|
||||
console.warn('⚠️ 课程详情API返回格式异常:', response)
|
||||
return {
|
||||
code: 500,
|
||||
message: response.data?.message || '获取课程详情失败',
|
||||
data: {} as Course
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('❌ 获取课程详情失败:', error)
|
||||
return {
|
||||
code: 500,
|
||||
message: error.message || '获取课程详情失败',
|
||||
data: {} as Course
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索课程
|
||||
static searchCourses(params: SearchRequest): Promise<ApiResponse<PaginationResponse<Course>>> {
|
||||
return ApiRequest.get('/courses/search', params)
|
||||
@ -246,86 +315,89 @@ export class CourseApi {
|
||||
}
|
||||
|
||||
// 获取课程详情 - 适配后端接口
|
||||
static async getCourseById(id: number): Promise<ApiResponse<Course>> {
|
||||
static async getCourseById(id: string): Promise<ApiResponse<Course>> {
|
||||
try {
|
||||
// 调用后端课程详情接口
|
||||
const response = await ApiRequest.get<BackendCourse>('/lesson/detail', { id })
|
||||
const response = await ApiRequest.get<any>('/biz/course/detail', { id })
|
||||
|
||||
// 检查是否是axios响应格式还是我们的ApiResponse格式
|
||||
let actualData: any
|
||||
let actualCode: number
|
||||
let actualMessage: string
|
||||
console.log('🔍 课程详情API响应:', response)
|
||||
|
||||
// 使用类型断言来处理不同的响应格式
|
||||
const responseAny = response as any
|
||||
|
||||
if (responseAny.data && typeof responseAny.data === 'object' && 'data' in responseAny.data) {
|
||||
// 这是我们期望的ApiResponse格式: { code, message, data: BackendCourse }
|
||||
actualData = responseAny.data.data
|
||||
actualCode = responseAny.data.code
|
||||
actualMessage = responseAny.data.message
|
||||
console.log('检测到ApiResponse格式')
|
||||
} else {
|
||||
// 这可能是直接的axios响应格式: BackendCourse
|
||||
actualData = responseAny.data
|
||||
actualCode = responseAny.status || 200
|
||||
actualMessage = responseAny.statusText || 'OK'
|
||||
console.log('检测到直接响应格式')
|
||||
// 处理后端响应格式
|
||||
if (response.data && response.data.success) {
|
||||
// 检查result是否为null或空
|
||||
if (!response.data.result) {
|
||||
console.warn('⚠️ 课程详情为空,可能课程不存在或已删除')
|
||||
return {
|
||||
code: 404,
|
||||
message: '课程不存在或已删除',
|
||||
data: {} as Course
|
||||
}
|
||||
|
||||
// 适配数据格式
|
||||
const adaptedCourse: Course = {
|
||||
id: actualData.id,
|
||||
title: actualData.name,
|
||||
description: actualData.description,
|
||||
content: actualData.outline, // 使用 outline 作为课程内容
|
||||
thumbnail: actualData.cover,
|
||||
coverImage: actualData.cover,
|
||||
price: parseFloat(actualData.price || '0'),
|
||||
originalPrice: parseFloat(actualData.price || '0'),
|
||||
}
|
||||
// 转换后端数据格式为前端格式
|
||||
const item: BackendCourseItem = response.data.result
|
||||
const course: Course = {
|
||||
id: item.id, // 保持字符串格式,不转换为数字
|
||||
title: item.name || '',
|
||||
description: item.description || '',
|
||||
thumbnail: item.cover || '',
|
||||
price: 0, // 后端没有价格字段,设为0
|
||||
currency: 'CNY',
|
||||
rating: 4.5,
|
||||
rating: 0, // 后端没有评分字段,设为0
|
||||
ratingCount: 0,
|
||||
studentsCount: 0,
|
||||
duration: this.calculateDuration(actualData.startTime, actualData.endTime),
|
||||
studentsCount: item.enrollCount || 0,
|
||||
duration: item.arrangement || '待定',
|
||||
totalLessons: 0,
|
||||
level: 'beginner' as const,
|
||||
level: this.mapDifficultyToStandardLevel(item.difficulty),
|
||||
language: 'zh-CN',
|
||||
category: {
|
||||
id: actualData.categoryId,
|
||||
name: '未分类',
|
||||
slug: 'uncategorized'
|
||||
id: 1,
|
||||
name: item.subject || '其他',
|
||||
slug: 'other'
|
||||
},
|
||||
tags: [],
|
||||
skills: [],
|
||||
requirements: actualData.prerequisite ? [actualData.prerequisite] : [],
|
||||
objectives: actualData.target ? [actualData.target] : [],
|
||||
requirements: item.prerequisite ? [item.prerequisite] : [],
|
||||
objectives: item.target ? [item.target] : [],
|
||||
instructor: {
|
||||
id: actualData.teacherId || 0,
|
||||
name: actualData.school || '未知讲师',
|
||||
id: 1,
|
||||
name: item.school || '未知讲师',
|
||||
title: '讲师',
|
||||
bio: actualData.position || '',
|
||||
bio: '',
|
||||
avatar: '',
|
||||
rating: 4.5,
|
||||
studentsCount: 0,
|
||||
coursesCount: 0,
|
||||
experience: actualData.arrangement || '',
|
||||
experience: '',
|
||||
education: [],
|
||||
certifications: []
|
||||
},
|
||||
status: 'published' as const,
|
||||
createdAt: this.formatTimestamp(actualData.createdTime),
|
||||
updatedAt: this.formatTimestamp(actualData.updatedTime),
|
||||
publishedAt: actualData.startTime
|
||||
status: item.status === 1 ? 'published' : 'draft',
|
||||
isEnrolled: false,
|
||||
progress: 0,
|
||||
createdAt: this.formatTimestamp(item.createTime),
|
||||
updatedAt: this.formatTimestamp(item.updateTime)
|
||||
}
|
||||
|
||||
return {
|
||||
code: actualCode,
|
||||
message: actualMessage,
|
||||
data: adaptedCourse
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: course
|
||||
}
|
||||
} else {
|
||||
console.warn('⚠️ 课程详情API返回格式异常:', response)
|
||||
return {
|
||||
code: 500,
|
||||
message: response.data?.message || '获取课程详情失败',
|
||||
data: {} as Course
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('❌ 获取课程详情失败:', error)
|
||||
return {
|
||||
code: 500,
|
||||
message: error.message || '获取课程详情失败',
|
||||
data: {} as Course
|
||||
}
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@ -355,8 +427,135 @@ export class CourseApi {
|
||||
}
|
||||
|
||||
// 获取课程分类
|
||||
static getCategories(): Promise<ApiResponse<CourseCategory[]>> {
|
||||
return ApiRequest.get('/categories')
|
||||
static async getCategories(): Promise<ApiResponse<CourseCategory[]>> {
|
||||
try {
|
||||
console.log('🚀 获取课程分类列表')
|
||||
|
||||
// 调用后端API(不需要token)
|
||||
const response = await ApiRequest.get<any>('/biz/course/category/list')
|
||||
|
||||
console.log('🔍 分类API响应:', response)
|
||||
|
||||
// 处理后端响应格式
|
||||
if (response.data && response.data.success && response.data.result) {
|
||||
// 转换后端数据格式为前端格式
|
||||
const categories: CourseCategory[] = response.data.result.map((item: any) => ({
|
||||
id: parseInt(item.id) || 0,
|
||||
name: item.name || '',
|
||||
slug: item.name?.toLowerCase().replace(/\s+/g, '-') || '',
|
||||
description: '',
|
||||
sortOrder: item.sortOrder || 0
|
||||
}))
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: categories
|
||||
}
|
||||
} else {
|
||||
console.warn('⚠️ 分类API返回格式异常:', response)
|
||||
return {
|
||||
code: 500,
|
||||
message: response.data?.message || '获取分类失败',
|
||||
data: []
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('❌ 获取课程分类失败:', error)
|
||||
return {
|
||||
code: 500,
|
||||
message: error.message || '获取分类失败',
|
||||
data: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取课程专题列表
|
||||
static async getSubjects(): Promise<ApiResponse<CourseSubject[]>> {
|
||||
try {
|
||||
console.log('🚀 获取课程专题列表')
|
||||
|
||||
// 调用后端API(不需要token)
|
||||
const response = await ApiRequest.get<any>('/biz/course/subject/list')
|
||||
|
||||
console.log('🔍 专题API响应:', response)
|
||||
|
||||
// 处理后端响应格式
|
||||
if (response.data && response.data.success && response.data.result) {
|
||||
// 转换后端数据格式为前端格式(专题接口返回的是value和label字段)
|
||||
const subjects: CourseSubject[] = response.data.result.map((item: any, index: number) => ({
|
||||
id: item.value || '0', // 保持原始的value值(字符串格式)
|
||||
name: item.label || '',
|
||||
slug: item.label?.toLowerCase().replace(/\s+/g, '-') || '',
|
||||
description: '',
|
||||
sortOrder: index
|
||||
}))
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: subjects
|
||||
}
|
||||
} else {
|
||||
console.warn('⚠️ 专题API返回格式异常:', response)
|
||||
return {
|
||||
code: 500,
|
||||
message: response.data?.message || '获取专题失败',
|
||||
data: []
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('❌ 获取课程专题失败:', error)
|
||||
return {
|
||||
code: 500,
|
||||
message: error.message || '获取专题失败',
|
||||
data: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取课程难度列表
|
||||
static async getDifficulties(): Promise<ApiResponse<CourseDifficulty[]>> {
|
||||
try {
|
||||
console.log('🚀 获取课程难度列表')
|
||||
|
||||
// 调用后端API(不需要token)
|
||||
const response = await ApiRequest.get<any>('/biz/course/difficulty/list')
|
||||
|
||||
console.log('🔍 难度API响应:', response)
|
||||
|
||||
// 处理后端响应格式
|
||||
if (response.data && response.data.success && response.data.result) {
|
||||
// 转换后端数据格式为前端格式(难度接口返回的是value和label字段)
|
||||
const difficulties: CourseDifficulty[] = response.data.result.map((item: any, index: number) => ({
|
||||
id: item.value || '0', // 保持原始的value值(字符串格式)
|
||||
name: item.label || '',
|
||||
slug: item.label?.toLowerCase().replace(/\s+/g, '-') || '',
|
||||
description: '',
|
||||
sortOrder: index
|
||||
}))
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: difficulties
|
||||
}
|
||||
} else {
|
||||
console.warn('⚠️ 难度API返回格式异常:', response)
|
||||
return {
|
||||
code: 500,
|
||||
message: response.data?.message || '获取难度失败',
|
||||
data: []
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('❌ 获取课程难度失败:', error)
|
||||
return {
|
||||
code: 500,
|
||||
message: error.message || '获取难度失败',
|
||||
data: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取分类下的课程
|
||||
|
@ -12,9 +12,6 @@ const checkNetworkStatus = (): boolean => {
|
||||
return true // 默认认为网络可用
|
||||
}
|
||||
|
||||
// 全局开关:强制使用Mock数据(当前需求:全部使用Mock,不访问后端)
|
||||
const FORCE_ENABLE_MOCK = true
|
||||
|
||||
// 消息提示函数 - 使用window.alert作为fallback,实际项目中应该使用UI库的消息组件
|
||||
const showMessage = (message: string, type: 'success' | 'error' | 'warning' | 'info' = 'info') => {
|
||||
// 这里可以替换为你使用的UI库的消息组件
|
||||
@ -30,21 +27,36 @@ const showMessage = (message: string, type: 'success' | 'error' | 'warning' | 'i
|
||||
|
||||
// 创建axios实例
|
||||
const request: AxiosInstance = axios.create({
|
||||
// 统一走 /api,由 Vite 代理到后端,避免CORS;若启用Mock,则只会走本地Mock
|
||||
baseURL: '/api',
|
||||
baseURL: import.meta.env.VITE_API_BASE_URL || '/jeecgboot',
|
||||
timeout: 30000, // 增加到30秒
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
// 不需要token的接口列表
|
||||
const NO_TOKEN_URLS = [
|
||||
'/biz/course/category/list',
|
||||
'/biz/course/subject/list',
|
||||
'/biz/course/difficulty/list',
|
||||
'/biz/course/list',
|
||||
'/biz/course/detail',
|
||||
// 可以在这里添加其他不需要token的接口
|
||||
]
|
||||
|
||||
// 请求拦截器
|
||||
request.interceptors.request.use(
|
||||
(config: InternalAxiosRequestConfig) => {
|
||||
// 添加认证token
|
||||
// 检查是否需要添加token
|
||||
const needToken = !NO_TOKEN_URLS.some(url => config.url?.includes(url))
|
||||
|
||||
if (needToken) {
|
||||
// 添加认证token(直接传token,不需要Bearer前缀)
|
||||
const userStore = useUserStore()
|
||||
if (userStore.token) {
|
||||
config.headers.Authorization = `Bearer ${userStore.token}`
|
||||
const token = userStore.token || localStorage.getItem('X-Access-Token') || ''
|
||||
if (token) {
|
||||
config.headers['X-Access-Token'] = token
|
||||
}
|
||||
}
|
||||
|
||||
// 添加请求时间戳
|
||||
@ -180,242 +192,7 @@ request.interceptors.response.use(
|
||||
}
|
||||
)
|
||||
|
||||
// 导入Mock数据
|
||||
import { mockCourses, getMockCourseSections } from './mock/courses'
|
||||
// getMockCourseDetail 暂时注释,后续需要时再启用
|
||||
|
||||
// Mock数据处理
|
||||
const handleMockRequest = async <T = any>(url: string, method: string, data?: any): Promise<ApiResponse<T>> => {
|
||||
console.log('🚀 Mock Request:', { url, method, data })
|
||||
|
||||
// 模拟网络延迟
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
|
||||
// 登录Mock
|
||||
if (url === '/users/login' && method === 'POST') {
|
||||
const { email, phone, password } = data || {}
|
||||
const loginField = phone || email
|
||||
|
||||
// 模拟登录验证
|
||||
if (loginField && password) {
|
||||
return {
|
||||
code: 200,
|
||||
message: '登录成功',
|
||||
data: {
|
||||
user: {
|
||||
id: 1,
|
||||
email: email || `${phone}@example.com`,
|
||||
phone: phone || '123456789',
|
||||
username: phone || email?.split('@')[0] || 'user',
|
||||
nickname: '测试用户',
|
||||
avatar: 'https://via.placeholder.com/100',
|
||||
role: 'student',
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
updatedAt: '2024-01-01T00:00:00Z'
|
||||
},
|
||||
token: 'mock_jwt_token_' + Date.now(),
|
||||
refreshToken: 'mock_refresh_token_' + Date.now(),
|
||||
expiresIn: 3600
|
||||
}
|
||||
} as ApiResponse<T>
|
||||
} else {
|
||||
return {
|
||||
code: 400,
|
||||
message: '手机号/邮箱或密码不能为空',
|
||||
data: null
|
||||
} as ApiResponse<T>
|
||||
}
|
||||
}
|
||||
|
||||
// 注册Mock
|
||||
if (url === '/auth/register' && method === 'POST') {
|
||||
const { email, password, verificationCode } = data || {}
|
||||
|
||||
if (!email || !password) {
|
||||
return {
|
||||
code: 400,
|
||||
message: '邮箱和密码不能为空',
|
||||
data: null
|
||||
} as ApiResponse<T>
|
||||
}
|
||||
|
||||
if (!verificationCode) {
|
||||
return {
|
||||
code: 400,
|
||||
message: '验证码不能为空',
|
||||
data: null
|
||||
} as ApiResponse<T>
|
||||
}
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: '注册成功',
|
||||
data: {
|
||||
id: 2,
|
||||
email: email,
|
||||
username: email.split('@')[0],
|
||||
nickname: '新用户',
|
||||
avatar: '',
|
||||
role: 'student',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
}
|
||||
} as ApiResponse<T>
|
||||
}
|
||||
|
||||
// 发送验证码Mock
|
||||
if (url === '/auth/send-verification' && method === 'POST') {
|
||||
return {
|
||||
code: 200,
|
||||
message: '验证码已发送',
|
||||
data: null
|
||||
} as ApiResponse<T>
|
||||
}
|
||||
|
||||
// 获取当前用户信息Mock
|
||||
if (url === '/auth/me' && method === 'GET') {
|
||||
return {
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
id: 1,
|
||||
email: 'test@example.com',
|
||||
username: 'test',
|
||||
nickname: '测试用户',
|
||||
avatar: '',
|
||||
role: 'student',
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
updatedAt: '2024-01-01T00:00:00Z'
|
||||
}
|
||||
} as ApiResponse<T>
|
||||
}
|
||||
|
||||
// 用户登出Mock
|
||||
if (url === '/auth/logout' && method === 'POST') {
|
||||
return {
|
||||
code: 200,
|
||||
message: '登出成功',
|
||||
data: null
|
||||
} as ApiResponse<T>
|
||||
}
|
||||
|
||||
// 课程详情Mock
|
||||
if (url === '/lesson/detail' && method === 'GET') {
|
||||
// 对于GET请求,参数直接在data中(data就是params对象)
|
||||
const id = data?.id
|
||||
console.log('课程详情Mock - 获取到的ID:', id, '原始data:', data)
|
||||
|
||||
if (!id) {
|
||||
return {
|
||||
code: 400,
|
||||
message: '课程ID必填',
|
||||
data: null
|
||||
} as ApiResponse<T>
|
||||
}
|
||||
|
||||
// 根据课程ID提供不同的模拟数据
|
||||
const courseData = {
|
||||
1: {
|
||||
name: 'DeepSeek大语言模型实战应用',
|
||||
cover: 'https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=400&h=300&fit=crop',
|
||||
price: '299.00',
|
||||
school: 'DeepSeek技术学院',
|
||||
description: '本课程深度聚焦DeepSeek大语言模型的实际应用,让每一位学员了解并学习使用DeepSeek,结合办公自动化职业岗位标准,以实际工作任务为引导,强调课程内容的易用性和岗位要求的匹配性。',
|
||||
position: 'AI技术专家 / 高级讲师'
|
||||
},
|
||||
2: {
|
||||
name: 'Python编程基础与实战',
|
||||
cover: 'https://images.unsplash.com/photo-1526379095098-d400fd0bf935?w=400&h=300&fit=crop',
|
||||
price: '199.00',
|
||||
school: '编程技术学院',
|
||||
description: '从零开始学习Python编程,涵盖基础语法、数据结构、面向对象编程等核心概念,通过实际项目练习掌握Python开发技能。',
|
||||
position: 'Python开发专家 / 资深讲师'
|
||||
},
|
||||
3: {
|
||||
name: 'Web前端开发全栈课程',
|
||||
cover: 'https://images.unsplash.com/photo-1461749280684-dccba630e2f6?w=400&h=300&fit=crop',
|
||||
price: '399.00',
|
||||
school: '前端技术学院',
|
||||
description: '全面学习现代Web前端开发技术,包括HTML5、CSS3、JavaScript、Vue.js、React等主流框架,培养全栈开发能力。',
|
||||
position: '前端架构师 / 技术总监'
|
||||
}
|
||||
}
|
||||
|
||||
const currentCourse = courseData[id as keyof typeof courseData] || courseData[1]
|
||||
|
||||
// 模拟课程详情数据
|
||||
return {
|
||||
code: 0,
|
||||
message: '查询课程详情成功',
|
||||
data: {
|
||||
id: parseInt(id),
|
||||
name: currentCourse.name,
|
||||
cover: currentCourse.cover,
|
||||
categoryId: 1,
|
||||
price: currentCourse.price,
|
||||
school: currentCourse.school,
|
||||
description: currentCourse.description,
|
||||
teacherId: 1,
|
||||
outline: '<div><h4>课程大纲:</h4><ul><li><strong>第一章:基础入门</strong><br/>- 环境搭建与配置<br/>- 基本概念理解<br/>- 实践操作演示</li><li><strong>第二章:核心技能</strong><br/>- 核心功能详解<br/>- 实际应用场景<br/>- 案例分析讲解</li><li><strong>第三章:高级应用</strong><br/>- 进阶技巧掌握<br/>- 项目实战演练<br/>- 问题解决方案</li></ul></div>',
|
||||
prerequisite: '具备基本的计算机操作能力',
|
||||
target: '掌握核心技能,能够在实际工作中熟练应用',
|
||||
arrangement: '理论与实践相结合,循序渐进的学习方式',
|
||||
startTime: '2025-01-26 10:13:17',
|
||||
endTime: '2025-03-26 10:13:17',
|
||||
revision: 1,
|
||||
position: currentCourse.position,
|
||||
createdAt: 1737944724,
|
||||
updatedAt: 1737944724,
|
||||
updatedTime: null
|
||||
}
|
||||
} as ApiResponse<T>
|
||||
}
|
||||
|
||||
// 课程列表Mock
|
||||
if (url === '/lesson/list' && method === 'GET') {
|
||||
return {
|
||||
code: 0,
|
||||
message: '查询课程列表成功',
|
||||
data: {
|
||||
list: mockCourses,
|
||||
total: mockCourses.length
|
||||
}
|
||||
} as ApiResponse<T>
|
||||
}
|
||||
|
||||
// 课程章节列表Mock
|
||||
if (url === '/lesson/section/list' && method === 'GET') {
|
||||
const lessonId = data?.lesson_id
|
||||
console.log('课程章节Mock - 获取到的lesson_id:', lessonId, '原始data:', data)
|
||||
|
||||
if (!lessonId) {
|
||||
return {
|
||||
code: 400,
|
||||
message: '课程ID必填',
|
||||
data: null
|
||||
} as ApiResponse<T>
|
||||
}
|
||||
|
||||
const mockSections = getMockCourseSections(parseInt(lessonId))
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
list: mockSections,
|
||||
total: mockSections.length
|
||||
},
|
||||
timestamp: new Date().toISOString()
|
||||
} as ApiResponse<T>
|
||||
}
|
||||
|
||||
// 默认404响应
|
||||
return {
|
||||
code: 404,
|
||||
message: '接口不存在',
|
||||
data: null
|
||||
} as ApiResponse<T>
|
||||
}
|
||||
|
||||
// 重试机制
|
||||
const retryRequest = async <T = any>(
|
||||
@ -459,19 +236,7 @@ export class ApiRequest {
|
||||
params?: any,
|
||||
config?: AxiosRequestConfig
|
||||
): Promise<ApiResponse<T>> {
|
||||
const enableMock = FORCE_ENABLE_MOCK || ((import.meta as any).env?.VITE_ENABLE_MOCK === 'true')
|
||||
// 优先:若显式启用Mock,直接使用Mock
|
||||
if (enableMock) {
|
||||
return handleMockRequest<T>(url, 'GET', params)
|
||||
}
|
||||
|
||||
try {
|
||||
return await retryRequest(() => request.get(url, { params, ...config }))
|
||||
} catch (error) {
|
||||
console.warn('API请求失败,降级到Mock数据:', error)
|
||||
// 后备:真实API失败时,仍回落到Mock,保障页面可用
|
||||
return handleMockRequest<T>(url, 'GET', params)
|
||||
}
|
||||
}
|
||||
|
||||
// POST 请求
|
||||
@ -480,17 +245,7 @@ export class ApiRequest {
|
||||
data?: any,
|
||||
config?: AxiosRequestConfig
|
||||
): Promise<ApiResponse<T>> {
|
||||
const enableMock = FORCE_ENABLE_MOCK || ((import.meta as any).env?.VITE_ENABLE_MOCK === 'true')
|
||||
if (enableMock) {
|
||||
return handleMockRequest<T>(url, 'POST', data)
|
||||
}
|
||||
|
||||
try {
|
||||
return await retryRequest(() => request.post(url, data, config))
|
||||
} catch (error) {
|
||||
console.warn('API请求失败,降级到Mock数据:', error)
|
||||
return handleMockRequest<T>(url, 'POST', data)
|
||||
}
|
||||
}
|
||||
|
||||
// PUT 请求
|
||||
|
129
src/api/types.ts
129
src/api/types.ts
@ -50,6 +50,7 @@ export interface UserProfile {
|
||||
export interface LoginRequest {
|
||||
email?: string
|
||||
phone?: string
|
||||
username?: string // 新增username字段,用于适配后端API
|
||||
password: string
|
||||
captcha?: string
|
||||
}
|
||||
@ -81,7 +82,7 @@ export interface RegisterRequest {
|
||||
|
||||
// 课程相关类型
|
||||
export interface Course {
|
||||
id: number
|
||||
id: string // 改为string类型,保持后端ID的原始格式
|
||||
title: string
|
||||
subtitle?: string
|
||||
description: string
|
||||
@ -175,6 +176,132 @@ export interface CourseCategory {
|
||||
children?: CourseCategory[]
|
||||
}
|
||||
|
||||
// 后端分类数据格式
|
||||
export interface BackendCourseCategory {
|
||||
id: string
|
||||
name: string
|
||||
sortOrder: number
|
||||
createBy: string
|
||||
createTime: string
|
||||
updateBy: string
|
||||
updateTime: string
|
||||
}
|
||||
|
||||
// 后端分类列表响应格式
|
||||
export interface BackendCourseCategoryListResponse {
|
||||
success: boolean
|
||||
message: string
|
||||
code: number
|
||||
result: BackendCourseCategory[]
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
// 后端专题数据格式(字典值格式)
|
||||
export interface BackendCourseSubject {
|
||||
value: string
|
||||
label: string
|
||||
}
|
||||
|
||||
// 前端专题数据格式
|
||||
export interface CourseSubject {
|
||||
id: string // 改为string类型,保持后端value字段的原始格式
|
||||
name: string
|
||||
slug: string
|
||||
description?: string
|
||||
sortOrder: number
|
||||
}
|
||||
|
||||
// 后端专题列表响应格式
|
||||
export interface BackendCourseSubjectListResponse {
|
||||
success: boolean
|
||||
message: string
|
||||
code: number
|
||||
result: BackendCourseSubject[]
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
// 后端难度数据格式(字典值格式)
|
||||
export interface BackendCourseDifficulty {
|
||||
value: string
|
||||
label: string
|
||||
}
|
||||
|
||||
// 前端难度数据格式
|
||||
export interface CourseDifficulty {
|
||||
id: string // 改为string类型,保持后端value字段的原始格式
|
||||
name: string
|
||||
slug: string
|
||||
description?: string
|
||||
sortOrder: number
|
||||
}
|
||||
|
||||
// 后端难度列表响应格式
|
||||
export interface BackendCourseDifficultyListResponse {
|
||||
success: boolean
|
||||
message: string
|
||||
code: number
|
||||
result: BackendCourseDifficulty[]
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
// 课程列表查询参数
|
||||
export interface CourseListQueryParams {
|
||||
categoryId?: string // 分类ID
|
||||
difficulty?: string // 难度值
|
||||
subject?: string // 专题值
|
||||
}
|
||||
|
||||
// 后端课程数据格式
|
||||
export interface BackendCourseItem {
|
||||
id: string
|
||||
name: string
|
||||
cover: string
|
||||
video: string
|
||||
school: string
|
||||
description: string
|
||||
type: number
|
||||
target: string
|
||||
difficulty: number
|
||||
subject: string
|
||||
outline: string
|
||||
prerequisite: string
|
||||
reference: string
|
||||
arrangement: string
|
||||
startTime: string
|
||||
endTime: string
|
||||
enrollCount: number
|
||||
maxEnroll: number
|
||||
status: number
|
||||
question: string
|
||||
createBy: string
|
||||
createTime: string
|
||||
updateBy: string
|
||||
updateTime: string
|
||||
}
|
||||
|
||||
// 后端课程列表响应格式
|
||||
export interface BackendCourseListResponse {
|
||||
success: boolean
|
||||
message: string
|
||||
code: number
|
||||
result: BackendCourseItem[]
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
// 课程详情查询参数
|
||||
export interface CourseDetailQueryParams {
|
||||
id: string
|
||||
}
|
||||
|
||||
// 后端课程详情响应格式
|
||||
export interface BackendCourseDetailResponse {
|
||||
success: boolean
|
||||
message: string
|
||||
code: number
|
||||
result: BackendCourseItem
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
export interface Instructor {
|
||||
id: number
|
||||
name: string
|
||||
|
@ -100,15 +100,21 @@ const handleLogin = async () => {
|
||||
isLoading.value = true
|
||||
try {
|
||||
console.log('🚀 开始登录:', { account: loginForm.account, password: '***' })
|
||||
console.log('🔍 表单密码长度:', loginForm.password?.length)
|
||||
console.log('🔍 表单密码内容:', loginForm.password)
|
||||
|
||||
// 判断输入的是手机号还是邮箱
|
||||
const isPhone = /^[0-9]+$/.test(loginForm.account)
|
||||
|
||||
// 调用登录API
|
||||
const response = await AuthApi.login({
|
||||
const loginParams = {
|
||||
...(isPhone ? { phone: loginForm.account } : { email: loginForm.account }),
|
||||
password: loginForm.password
|
||||
})
|
||||
}
|
||||
|
||||
console.log('🔍 准备发送的登录参数:', loginParams)
|
||||
|
||||
// 调用登录API
|
||||
const response = await AuthApi.login(loginParams)
|
||||
|
||||
console.log('✅ 登录响应:', response)
|
||||
|
||||
@ -120,6 +126,7 @@ const handleLogin = async () => {
|
||||
userStore.token = token
|
||||
|
||||
// 保存到本地存储
|
||||
localStorage.setItem('X-Access-Token', token)
|
||||
localStorage.setItem('token', token)
|
||||
localStorage.setItem('refreshToken', refreshToken || '')
|
||||
localStorage.setItem('user', JSON.stringify(user))
|
||||
|
@ -8,7 +8,7 @@ export interface User extends ApiUser {}
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
// 状态
|
||||
const user = ref<User | null>(null)
|
||||
const token = ref<string | null>(localStorage.getItem('token'))
|
||||
const token = ref<string | null>(localStorage.getItem('X-Access-Token'))
|
||||
const isLoading = ref(false)
|
||||
|
||||
// 计算属性
|
||||
@ -49,7 +49,7 @@ export const useUserStore = defineStore('user', () => {
|
||||
// 无论API调用是否成功,都清除本地数据
|
||||
user.value = null
|
||||
token.value = null
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('X-Access-Token')
|
||||
localStorage.removeItem('refreshToken')
|
||||
localStorage.removeItem('user')
|
||||
localStorage.removeItem('rememberMe')
|
||||
@ -136,7 +136,7 @@ export const useUserStore = defineStore('user', () => {
|
||||
|
||||
const initializeAuth = async () => {
|
||||
const savedUser = localStorage.getItem('user')
|
||||
const savedToken = localStorage.getItem('token')
|
||||
const savedToken = localStorage.getItem('X-Access-Token')
|
||||
|
||||
if (savedUser && savedToken) {
|
||||
try {
|
||||
|
@ -463,7 +463,7 @@ import RegisterModal from '@/components/auth/RegisterModal.vue'
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const courseId = ref(Number(route.params.id))
|
||||
const courseId = ref(route.params.id as string)
|
||||
const { loginModalVisible, registerModalVisible, handleAuthSuccess, showLoginModal } = useAuth()
|
||||
// enrollCourse 暂时未使用,后续需要时再启用
|
||||
|
||||
@ -715,7 +715,7 @@ const displayComments = ref([
|
||||
const loadCourseDetail = async () => {
|
||||
console.log('开始加载课程详情,课程ID:', courseId.value)
|
||||
|
||||
if (!courseId.value || isNaN(courseId.value)) {
|
||||
if (!courseId.value || courseId.value.trim() === '') {
|
||||
error.value = '课程ID无效'
|
||||
console.error('课程ID无效:', courseId.value)
|
||||
return
|
||||
@ -768,7 +768,7 @@ const loadCourseDetail = async () => {
|
||||
|
||||
// 加载课程章节列表
|
||||
const loadCourseSections = async () => {
|
||||
if (!courseId.value || isNaN(courseId.value)) {
|
||||
if (!courseId.value || courseId.value.trim() === '') {
|
||||
sectionsError.value = '课程ID无效'
|
||||
console.error('课程ID无效:', courseId.value)
|
||||
return
|
||||
@ -1131,7 +1131,7 @@ onMounted(() => {
|
||||
console.log('课程详情页加载完成,课程ID:', courseId.value)
|
||||
initializeMockState() // 初始化模拟状态
|
||||
loadCourseDetail()
|
||||
loadCourseSections()
|
||||
// loadCourseSections() // 暂时禁用章节接口调用,因为接口不存在
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -17,70 +17,38 @@
|
||||
<div class="filter-group">
|
||||
<span class="filter-label">类型:</span>
|
||||
<div class="filter-tags">
|
||||
<span class="filter-tag" :class="{ active: selectedSubject === '全部' }"
|
||||
@click="selectSubject('全部')">全部</span>
|
||||
<span class="filter-tag" :class="{ active: selectedSubject === '必修课' }"
|
||||
@click="selectSubject('必修课')">必修课</span>
|
||||
<span class="filter-tag" :class="{ active: selectedSubject === '高分课' }"
|
||||
@click="selectSubject('高分课')">高分课</span>
|
||||
<span class="filter-tag" :class="{ active: selectedSubject === '名师课堂' }"
|
||||
@click="selectSubject('名师课堂')">名师课堂</span>
|
||||
<span class="filter-tag" :class="{ active: selectedSubject === '训练营' }"
|
||||
@click="selectSubject('训练营')">训练营</span>
|
||||
<span class="filter-tag" :class="{ active: selectedSubject === '无考试' }"
|
||||
@click="selectSubject('无考试')">无考试</span>
|
||||
<span class="filter-tag" :class="{ active: selectedSubject === '专题讲座' }"
|
||||
@click="selectSubject('专题讲座')">专题讲座</span>
|
||||
<span class="filter-tag" :class="{ active: selectedMajor === '全部' }"
|
||||
@click="selectMajor('全部')">全部</span>
|
||||
<span
|
||||
v-for="category in categories"
|
||||
:key="category.id"
|
||||
class="filter-tag"
|
||||
:class="{ active: selectedMajor === category.name }"
|
||||
@click="selectMajor(category.name)"
|
||||
>
|
||||
{{ category.name }}
|
||||
</span>
|
||||
<!-- 加载状态 -->
|
||||
<span v-if="categoriesLoading" class="filter-tag loading">加载中...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 专题分类第一行 -->
|
||||
<!-- 专题分类 -->
|
||||
<div class="filter-group">
|
||||
<span class="filter-label">专题:</span>
|
||||
<div class="filter-tags">
|
||||
<span class="filter-tag" :class="{ active: selectedMajor === '全部' }" @click="selectMajor('全部')">全部</span>
|
||||
<span class="filter-tag" :class="{ active: selectedMajor === '学科教研' }"
|
||||
@click="selectMajor('学科教研')">学科教研</span>
|
||||
<span class="filter-tag" :class="{ active: selectedMajor === '班级管理' }"
|
||||
@click="selectMajor('班级管理')">班级管理</span>
|
||||
<span class="filter-tag" :class="{ active: selectedMajor === '通识技能' }"
|
||||
@click="selectMajor('通识技能')">通识技能</span>
|
||||
<span class="filter-tag" :class="{ active: selectedMajor === '信息素养' }"
|
||||
@click="selectMajor('信息素养')">信息素养</span>
|
||||
<span class="filter-tag" :class="{ active: selectedMajor === '师风师德' }"
|
||||
@click="selectMajor('师风师德')">师风师德</span>
|
||||
<span class="filter-tag" :class="{ active: selectedMajor === '专题教育' }"
|
||||
@click="selectMajor('专题教育')">专题教育</span>
|
||||
<span class="filter-tag" :class="{ active: selectedMajor === '综合实践' }"
|
||||
@click="selectMajor('综合实践')">综合实践</span>
|
||||
<span class="filter-tag" :class="{ active: selectedMajor === '个人成长' }"
|
||||
@click="selectMajor('个人成长')">个人成长</span>
|
||||
<span class="filter-tag" :class="{ active: selectedMajor === '教学案例' }"
|
||||
@click="selectMajor('教学案例')">教学案例</span>
|
||||
<span class="filter-tag" :class="{ active: selectedMajor === '教育技术' }"
|
||||
@click="selectMajor('教育技术')">教育技术</span>
|
||||
<span class="filter-tag" :class="{ active: selectedMajor === '心理健康' }"
|
||||
@click="selectMajor('心理健康')">心理健康</span>
|
||||
<span class="filter-tag" :class="{ active: selectedMajor === '家校沟通' }"
|
||||
@click="selectMajor('家校沟通')">家校沟通</span>
|
||||
<span class="filter-tag" :class="{ active: selectedMajor === '课程设计' }"
|
||||
@click="selectMajor('课程设计')">课程设计</span>
|
||||
<span class="filter-tag" :class="{ active: selectedMajor === '教育政策' }"
|
||||
@click="selectMajor('教育政策')">教育政策</span>
|
||||
<span class="filter-tag" :class="{ active: selectedMajor === '教学评估' }"
|
||||
@click="selectMajor('教学评估')">教学评估</span>
|
||||
<span class="filter-tag" :class="{ active: selectedMajor === '创新教育' }"
|
||||
@click="selectMajor('创新教育')">创新教育</span>
|
||||
<span class="filter-tag" :class="{ active: selectedMajor === 'STEAM教育' }"
|
||||
@click="selectMajor('STEAM教育')">STEAM教育</span>
|
||||
<span class="filter-tag" :class="{ active: selectedMajor === '教育心理学' }"
|
||||
@click="selectMajor('教育心理学')">教育心理学</span>
|
||||
<span class="filter-tag" :class="{ active: selectedMajor === '差异化教学' }"
|
||||
@click="selectMajor('差异化教学')">差异化教学</span>
|
||||
<span class="filter-tag" :class="{ active: selectedMajor === '教育领导力' }"
|
||||
@click="selectMajor('教育领导力')">教育领导力</span>
|
||||
<span class="filter-tag" :class="{ active: selectedMajor === '在线教学' }"
|
||||
@click="selectMajor('在线教学')">在线教学</span>
|
||||
<span class="filter-tag" :class="{ active: selectedSubject === '全部' }" @click="selectSubject('全部')">全部</span>
|
||||
<span
|
||||
v-for="subject in subjects"
|
||||
:key="subject.id"
|
||||
class="filter-tag"
|
||||
:class="{ active: selectedSubject === subject.name }"
|
||||
@click="selectSubject(subject.name)"
|
||||
>
|
||||
{{ subject.name }}
|
||||
</span>
|
||||
<!-- 加载状态 -->
|
||||
<span v-if="subjectsLoading" class="filter-tag loading">加载中...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -91,14 +59,17 @@
|
||||
<div class="filter-tags">
|
||||
<span class="filter-tag" :class="{ active: selectedDifficulty === '全部' }"
|
||||
@click="selectDifficulty('全部')">全部</span>
|
||||
<span class="filter-tag" :class="{ active: selectedDifficulty === '零基础' }"
|
||||
@click="selectDifficulty('零基础')">零基础</span>
|
||||
<span class="filter-tag" :class="{ active: selectedDifficulty === '初级' }"
|
||||
@click="selectDifficulty('初级')">初级</span>
|
||||
<span class="filter-tag" :class="{ active: selectedDifficulty === '中级' }"
|
||||
@click="selectDifficulty('中级')">中级</span>
|
||||
<span class="filter-tag" :class="{ active: selectedDifficulty === '高级' }"
|
||||
@click="selectDifficulty('高级')">高级</span>
|
||||
<span
|
||||
v-for="difficulty in difficulties"
|
||||
:key="difficulty.id"
|
||||
class="filter-tag"
|
||||
:class="{ active: selectedDifficulty === difficulty.name }"
|
||||
@click="selectDifficulty(difficulty.name)"
|
||||
>
|
||||
{{ difficulty.name }}
|
||||
</span>
|
||||
<!-- 加载状态 -->
|
||||
<span v-if="difficultiesLoading" class="filter-tag loading">加载中...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -118,9 +89,9 @@
|
||||
|
||||
<!-- 排序标签 -->
|
||||
<div class="sort-tabs">
|
||||
<span class="sort-tab" :class="{ active: selectedSort === '最新' }" @click="selectSort('最新')">最新</span>
|
||||
<span class="sort-tab" :class="{ active: selectedSort === '最热' }" @click="selectSort('最热')">最热</span>
|
||||
<span class="sort-tab" :class="{ active: selectedSort === '推荐' }" @click="selectSort('推荐')">推荐</span>
|
||||
<span class="sort-tab">最新</span>
|
||||
<span class="sort-tab">最热</span>
|
||||
<span class="sort-tab active">推荐</span>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
@ -203,8 +174,9 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import type { Course } from '@/api/types'
|
||||
import type { Course, CourseCategory, CourseSubject, CourseDifficulty, CourseListQueryParams } from '@/api/types'
|
||||
import { mockCourses } from '@/data/mockCourses'
|
||||
import { CourseApi } from '@/api'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@ -213,6 +185,18 @@ const courses = ref<Course[]>([])
|
||||
const loading = ref(false)
|
||||
const total = ref(0)
|
||||
|
||||
// 分类数据和加载状态
|
||||
const categories = ref<CourseCategory[]>([])
|
||||
const categoriesLoading = ref(false)
|
||||
|
||||
// 专题数据和加载状态
|
||||
const subjects = ref<CourseSubject[]>([])
|
||||
const subjectsLoading = ref(false)
|
||||
|
||||
// 难度数据和加载状态
|
||||
const difficulties = ref<CourseDifficulty[]>([])
|
||||
const difficultiesLoading = ref(false)
|
||||
|
||||
// 筛选状态
|
||||
const selectedSubject = ref('全部')
|
||||
const selectedMajor = ref('全部')
|
||||
@ -224,9 +208,6 @@ const itemsPerPage = 20
|
||||
const totalItems = computed(() => total.value)
|
||||
const totalPages = computed(() => Math.ceil(totalItems.value / itemsPerPage))
|
||||
|
||||
// 排序相关状态
|
||||
const selectedSort = ref('推荐')
|
||||
|
||||
// 控制广告显示状态
|
||||
const showAdvertisement = ref(true)
|
||||
|
||||
@ -276,84 +257,59 @@ const visiblePages = computed(() => {
|
||||
return pages
|
||||
})
|
||||
|
||||
// 排序切换函数
|
||||
const selectSort = (sort: string) => {
|
||||
selectedSort.value = sort
|
||||
currentPage.value = 1 // 重置到第一页
|
||||
loadCourses()
|
||||
}
|
||||
|
||||
// 加载课程数据(使用模拟数据)
|
||||
// 加载课程数据(使用真实API)
|
||||
const loadCourses = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
console.log('🚀 加载课程数据...')
|
||||
|
||||
// 模拟网络延迟
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
// 构建查询参数
|
||||
const queryParams: any = {}
|
||||
|
||||
// 筛选逻辑
|
||||
let filteredCourses = [...mockCourses]
|
||||
|
||||
// 按学科筛选
|
||||
if (selectedSubject.value !== '全部') {
|
||||
filteredCourses = filteredCourses.filter(course => {
|
||||
switch (selectedSubject.value) {
|
||||
case '计算机':
|
||||
return course.category.name === '编程开发' || course.category.name === '前端开发' || course.category.name === '后端开发'
|
||||
case '教育学':
|
||||
return course.category.name === '教育培训'
|
||||
default:
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 排序逻辑
|
||||
switch (selectedSort.value) {
|
||||
case '最新':
|
||||
filteredCourses.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
|
||||
break
|
||||
case '最热':
|
||||
filteredCourses.sort((a, b) => b.studentCount - a.studentCount)
|
||||
break
|
||||
case '推荐':
|
||||
default:
|
||||
// 推荐排序可以根据评分和学生数综合排序
|
||||
filteredCourses.sort((a, b) => (b.rating * 0.7 + b.studentCount * 0.3) - (a.rating * 0.7 + a.studentCount * 0.3))
|
||||
break
|
||||
}
|
||||
|
||||
// 按专业筛选
|
||||
// 根据选择的分类添加categoryId参数(分类接口返回的是{id, name}格式,传递id字段)
|
||||
if (selectedMajor.value !== '全部') {
|
||||
filteredCourses = filteredCourses.filter(course =>
|
||||
course.title.includes(selectedMajor.value) ||
|
||||
course.description.includes(selectedMajor.value) ||
|
||||
course.tags.some(tag => tag.includes(selectedMajor.value))
|
||||
)
|
||||
const selectedCategory = categories.value.find(cat => cat.name === selectedMajor.value)
|
||||
if (selectedCategory) {
|
||||
queryParams.categoryId = selectedCategory.id.toString()
|
||||
console.log('🏷️ 选择的分类:', selectedCategory.name, 'ID:', selectedCategory.id)
|
||||
}
|
||||
}
|
||||
|
||||
// 按难度筛选
|
||||
// 根据选择的难度添加difficulty参数(难度接口返回的是{value, label}格式,传递value字段)
|
||||
if (selectedDifficulty.value !== '全部') {
|
||||
const difficultyMap: { [key: string]: string } = {
|
||||
'初级': 'beginner',
|
||||
'中级': 'intermediate',
|
||||
'高级': 'advanced'
|
||||
}
|
||||
const targetLevel = difficultyMap[selectedDifficulty.value]
|
||||
if (targetLevel) {
|
||||
filteredCourses = filteredCourses.filter(course => course.level === targetLevel)
|
||||
const selectedDiff = difficulties.value.find(diff => diff.name === selectedDifficulty.value)
|
||||
if (selectedDiff) {
|
||||
queryParams.difficulty = selectedDiff.id // 直接使用字符串值,不需要toString()
|
||||
console.log('📊 选择的难度:', selectedDiff.name, 'Value:', selectedDiff.id)
|
||||
}
|
||||
}
|
||||
|
||||
// 分页处理
|
||||
total.value = filteredCourses.length
|
||||
const startIndex = (currentPage.value - 1) * itemsPerPage
|
||||
const endIndex = startIndex + itemsPerPage
|
||||
courses.value = filteredCourses.slice(startIndex, endIndex)
|
||||
// 根据选择的专题添加subject参数(专题接口返回的是{value, label}格式,传递value字段)
|
||||
if (selectedSubject.value !== '全部') {
|
||||
const selectedSubj = subjects.value.find(subj => subj.name === selectedSubject.value)
|
||||
if (selectedSubj) {
|
||||
queryParams.subject = selectedSubj.id // 直接使用字符串值,不需要toString()
|
||||
console.log('🎯 选择的专题:', selectedSubj.name, 'Value:', selectedSubj.id)
|
||||
}
|
||||
}
|
||||
|
||||
console.log('课程加载成功:', courses.value.length, '条课程,总计:', total.value)
|
||||
console.log('🔍 查询参数:', queryParams)
|
||||
|
||||
// 调用API
|
||||
const response = await CourseApi.getCourses(queryParams)
|
||||
console.log('✅ 课程API响应:', response)
|
||||
|
||||
if (response.code === 200 && response.data) {
|
||||
courses.value = response.data
|
||||
total.value = response.data.length
|
||||
console.log('✅ 课程数据加载成功:', courses.value.length, '条课程')
|
||||
} else {
|
||||
console.warn('⚠️ 课程数据加载失败:', response.message)
|
||||
courses.value = []
|
||||
total.value = 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载课程失败:', error)
|
||||
console.error('❌ 加载课程失败:', error)
|
||||
courses.value = []
|
||||
total.value = 0
|
||||
} finally {
|
||||
@ -442,9 +398,78 @@ const goToCourseDetail = (course: Course) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 加载课程分类数据
|
||||
const loadCategories = async () => {
|
||||
try {
|
||||
categoriesLoading.value = true
|
||||
console.log('🚀 加载课程分类...')
|
||||
|
||||
const response = await CourseApi.getCategories()
|
||||
console.log('✅ 分类API响应:', response)
|
||||
|
||||
if (response.code === 200 && response.data) {
|
||||
categories.value = response.data
|
||||
console.log('✅ 分类数据加载成功:', categories.value)
|
||||
} else {
|
||||
console.warn('⚠️ 分类数据加载失败:', response.message)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 加载分类数据失败:', error)
|
||||
} finally {
|
||||
categoriesLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 加载课程专题数据
|
||||
const loadSubjects = async () => {
|
||||
try {
|
||||
subjectsLoading.value = true
|
||||
console.log('🚀 加载课程专题...')
|
||||
|
||||
const response = await CourseApi.getSubjects()
|
||||
console.log('✅ 专题API响应:', response)
|
||||
|
||||
if (response.code === 200 && response.data) {
|
||||
subjects.value = response.data
|
||||
console.log('✅ 专题数据加载成功:', subjects.value)
|
||||
} else {
|
||||
console.warn('⚠️ 专题数据加载失败:', response.message)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 加载专题数据失败:', error)
|
||||
} finally {
|
||||
subjectsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 加载课程难度数据
|
||||
const loadDifficulties = async () => {
|
||||
try {
|
||||
difficultiesLoading.value = true
|
||||
console.log('🚀 加载课程难度...')
|
||||
|
||||
const response = await CourseApi.getDifficulties()
|
||||
console.log('✅ 难度API响应:', response)
|
||||
|
||||
if (response.code === 200 && response.data) {
|
||||
difficulties.value = response.data
|
||||
console.log('✅ 难度数据加载成功:', difficulties.value)
|
||||
} else {
|
||||
console.warn('⚠️ 难度数据加载失败:', response.message)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 加载难度数据失败:', error)
|
||||
} finally {
|
||||
difficultiesLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时加载数据
|
||||
onMounted(() => {
|
||||
loadCourses()
|
||||
loadCategories()
|
||||
loadSubjects()
|
||||
loadDifficulties()
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -617,6 +642,13 @@ onMounted(() => {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.filter-tag.loading {
|
||||
background: #f0f0f0;
|
||||
color: #999;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.sort-tabs {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
|
@ -1,15 +1,11 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), '')
|
||||
const proxyTarget = env.VITE_PROXY_TARGET || 'http://110.42.96.65:55510'
|
||||
|
||||
return {
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
vueDevTools(),
|
||||
@ -23,13 +19,9 @@ export default defineConfig(({ mode }) => {
|
||||
port: 3000,
|
||||
open: true,
|
||||
proxy: {
|
||||
// 将以 /api 开头的请求代理到后端,避免浏览器CORS限制
|
||||
'/api': {
|
||||
target: proxyTarget,
|
||||
changeOrigin: true,
|
||||
// 如果后端接口不是以 /api 开头,可在这里改写路径
|
||||
// rewrite: (path) => path.replace(/^\/api/, '')
|
||||
}
|
||||
'/jeecgboot': {
|
||||
target: 'http://103.40.14.23:25526',
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user