feat:练习讨论对接接口

This commit is contained in:
小张 2025-09-23 16:23:02 +08:00
parent 7aa5257cbd
commit ce54a41f4a
6 changed files with 299 additions and 96 deletions

View File

@ -176,6 +176,7 @@ export class AIApi {
const decoder = new TextDecoder() const decoder = new TextDecoder()
let buffer = '' let buffer = ''
// @ts-ignore
let messageEndReceived = false let messageEndReceived = false
let endTimeout: NodeJS.Timeout | null = null let endTimeout: NodeJS.Timeout | null = null
@ -330,20 +331,21 @@ export class AIApi {
* @returns Promise<void> * @returns Promise<void>
*/ */
static async sendChatMessageWithEventSource( static async sendChatMessageWithEventSource(
content: string, // @ts-ignore
content: string, // TODO: 需要在实际实现中使用此参数
onMessage: (chunk: string) => void, onMessage: (chunk: string) => void,
onComplete?: () => void, onComplete?: () => void,
onError?: (error: any) => void onError?: (error: any) => void
): Promise<void> { ): Promise<void> {
const request: AIChatRequest = { // const request: AIChatRequest = {
appId: "1970031066993217537", // appId: "1970031066993217537",
content: content, // content: content,
responseMode: "streaming" // responseMode: "streaming"
} // } // 暂时未使用
try { try {
// 获取token // 获取token
const token = localStorage.getItem('token') // const token = localStorage.getItem('token') // 暂时未使用
// 构建URL参数 // 构建URL参数
const url = new URL(`${import.meta.env.VITE_API_BASE_URL}/airag/chat/send`) const url = new URL(`${import.meta.env.VITE_API_BASE_URL}/airag/chat/send`)

View File

@ -916,6 +916,40 @@ export class CourseApi {
} }
} }
// 获取考试题目列表
static async getExamQuestions(examId: string, studentId: string): Promise<ApiResponse<any>> {
try {
console.log('🚀 调用获取考试题目API考试ID:', examId, '学生ID:', studentId)
// 直接在URL中添加studentId参数
const url = `/aiol/aiolExam/getExamQuestions/${examId}?studentId=${studentId}`
console.log('🔍 请求URL:', url)
const response = await ApiRequest.get<any>(url)
console.log('🔍 考试题目API响应:', response)
return response
} catch (error) {
console.error('❌ 获取考试题目失败:', error)
throw error
}
}
// 获取题目详情
static async getQuestionDetail(questionId: string): Promise<ApiResponse<any>> {
try {
console.log('🚀 调用获取题目详情API题目ID:', questionId)
const response = await ApiRequest.get<any>(`/aiol/aiolRepo/repoList/${questionId}`)
console.log('🔍 题目详情API响应:', response)
return response
} catch (error) {
console.error('❌ 获取题目详情失败:', error)
throw error
}
}
// 获取章节讨论 // 获取章节讨论
static async getSectionDiscussion(courseId: string, sectionId: string): Promise<ApiResponse<any>> { static async getSectionDiscussion(courseId: string, sectionId: string): Promise<ApiResponse<any>> {
try { try {

View File

@ -220,7 +220,7 @@ const categoryList = ref<CourseCategory[]>([]);
const repoOptions = computed(() => [ const repoOptions = computed(() => [
{ label: '全部题库', value: '' }, { label: '全部题库', value: '' },
...repoList.value.map((repo: Repo) => ({ ...repoList.value.map((repo: Repo) => ({
label: `${repo.title} (${repo.questionCount || 0}题)`, label: `${repo.title} (${repo.question_count || 0}题)`,
value: repo.id value: repo.id
})) }))
]); ]);

View File

@ -664,6 +664,9 @@ const createManualQualityButton = () => {
option.addEventListener('mouseleave', () => { option.addEventListener('mouseleave', () => {
if (quality.value !== props.currentQuality) { if (quality.value !== props.currentQuality) {
option.style.backgroundColor = 'transparent' option.style.backgroundColor = 'transparent'
} else {
//
option.style.backgroundColor = '#007bff'
} }
}) })
@ -698,6 +701,11 @@ const createManualQualityButton = () => {
// //
qualityButtonCreated.value = true qualityButtonCreated.value = true
//
setTimeout(() => {
updateQualityMenuStyles(props.currentQuality)
}, 100)
console.log('✅ 手动清晰度按钮创建完成') console.log('✅ 手动清晰度按钮创建完成')
} }
@ -707,6 +715,68 @@ const getCurrentQualityLabel = () => {
return current ? current.label : '360p' return current ? current.label : '360p'
} }
//
const updateQualityMenuStyles = (currentQualityValue: string) => {
const qualityMenu = dplayerContainer.value?.querySelector('.manual-quality .dplayer-quality-list')
if (qualityMenu) {
const qualityItems = qualityMenu.querySelectorAll('.dplayer-quality-item')
qualityItems.forEach((item, index) => {
const quality = props.videoQualities[index]
if (quality) {
const isActive = quality.value === currentQualityValue
const itemElement = item as HTMLElement
//
itemElement.style.cssText = `
padding: 8px 12px;
color: #fff;
font-size: 13px;
cursor: pointer;
transition: background-color 0.2s;
text-align: center;
${isActive ? 'background: #007bff;' : 'background: transparent;'}
`
//
const newItemElement = itemElement.cloneNode(true) as HTMLElement
itemElement.parentNode?.replaceChild(newItemElement, itemElement)
//
newItemElement.addEventListener('mouseenter', () => {
if (quality.value !== currentQualityValue) {
newItemElement.style.backgroundColor = 'rgba(255, 255, 255, 0.1)'
}
})
newItemElement.addEventListener('mouseleave', () => {
if (quality.value !== currentQualityValue) {
newItemElement.style.backgroundColor = 'transparent'
} else {
//
newItemElement.style.backgroundColor = '#007bff'
}
})
newItemElement.addEventListener('click', () => {
console.log('🔄 手动切换清晰度到:', quality.label)
switchQuality(quality)
;(qualityMenu as HTMLElement).style.display = 'none'
//
const qualityButton = dplayerContainer.value?.querySelector('.manual-quality .dplayer-quality-button')
if (qualityButton) {
qualityButton.textContent = quality.label
}
// updateQualityMenuStylesswitchQuality
})
console.log(`🎨 更新清晰度选项样式: ${quality.label} - ${isActive ? '激活' : '未激活'}`)
}
})
}
}
const switchQuality = (quality: any) => { const switchQuality = (quality: any) => {
console.log('🔄 开始切换清晰度:', { console.log('🔄 开始切换清晰度:', {
quality: quality, quality: quality,
@ -814,6 +884,9 @@ const switchQuality = (quality: any) => {
existingButton.textContent = quality.label existingButton.textContent = quality.label
console.log('✅ 清晰度按钮文字已立即更新为:', quality.label) console.log('✅ 清晰度按钮文字已立即更新为:', quality.label)
} }
//
updateQualityMenuStyles(quality.value)
} }

View File

@ -345,13 +345,13 @@
<div class="discussion-container"> <div class="discussion-container">
<!-- 讨论标题行 --> <!-- 讨论标题行 -->
<div class="discussion-title-row"> <div class="discussion-title-row">
<h2 class="discussion-title">讨论</h2> <h2 class="discussion-title">{{ discussionTitle }}</h2>
<span class="participation-status">未参与</span> <span class="participation-status">未参与</span>
</div> </div>
<!-- 讨论描述 --> <!-- 讨论描述 -->
<div class="discussion-description"> <div class="discussion-description">
如何理解学风与科研诚信如何理解优良学风与科研诚信之间的关系各位同学大家好欢迎加入本学期的课程学习在学习过程有任何问题困惑和不同见解都欢迎同学在讨论区积极交流踊跃回复老师留在讨论区的问题 {{ discussionDescription }}
</div> </div>
<!-- 评论统计 --> <!-- 评论统计 -->
@ -1347,6 +1347,7 @@ import { useMessage } from 'naive-ui'
import { CourseApi } from '@/api/modules/course' import { CourseApi } from '@/api/modules/course'
import { CommentApi } from '@/api/modules/comment' import { CommentApi } from '@/api/modules/comment'
import { AIApi } from '@/api/modules/ai' import { AIApi } from '@/api/modules/ai'
import { AuthApi } from '@/api/modules/auth'
import type { Course, CourseSection, CourseComment } from '@/api/types' import type { Course, CourseSection, CourseComment } from '@/api/types'
import QuillEditor from '@/components/common/QuillEditor.vue' import QuillEditor from '@/components/common/QuillEditor.vue'
import DPlayerVideo from '@/components/course/DPlayerVideo.vue' import DPlayerVideo from '@/components/course/DPlayerVideo.vue'
@ -1580,6 +1581,8 @@ const practiceFinished = ref(false)
const discussionMode = ref(false) const discussionMode = ref(false)
const currentDiscussionSection = ref<CourseSection | null>(null) const currentDiscussionSection = ref<CourseSection | null>(null)
const discussionList = ref<any[]>([]) const discussionList = ref<any[]>([])
const discussionTitle = ref('讨论')
const discussionDescription = ref('')
const newComment = ref('') const newComment = ref('')
const replyingTo = ref<any>(null) const replyingTo = ref<any>(null)
@ -2124,71 +2127,13 @@ const loadCourseSections = async () => {
console.log('✅ API返回的原始章节数据:', response.data.list) console.log('✅ API返回的原始章节数据:', response.data.list)
console.log('✅ 章节数据数量:', response.data.list.length) console.log('✅ 章节数据数量:', response.data.list.length)
// // 使API
const sectionsWithPractice = [...response.data.list] courseSections.value = response.data.list
groupedSections.value = groupSectionsByChapter(response.data.list)
//
const practiceSection: CourseSection = {
id: '999999', // 使ID
lessonId: '999999',
name: 'JavaScript基础练习',
type: 5, //
level: 2, //
parentId: sectionsWithPractice.find(s => s.level === 1)?.id || '1', //
duration: '30分钟',
completed: false,
outline: '',
sort: 999,
revision: 1,
createdAt: Date.now(),
updatedAt: Date.now(),
deletedAt: null
}
//
const firstChapterSections = sectionsWithPractice.filter(s => s.level === 2 && s.parentId === practiceSection.parentId)
if (firstChapterSections.length > 0) {
//
const insertIndex = sectionsWithPractice.findIndex(s => s.id === firstChapterSections[firstChapterSections.length - 1].id) + 1
sectionsWithPractice.splice(insertIndex, 0, practiceSection)
} else {
//
sectionsWithPractice.push(practiceSection)
}
//
const discussionSection: CourseSection = {
id: '999998', // 使ID
lessonId: '999998',
name: '机器学习与科研流程讨论',
type: 6, //
level: 2, //
parentId: sectionsWithPractice.find(s => s.level === 1)?.id || '1', //
duration: '讨论',
completed: false,
outline: '',
sort: 998,
revision: 1,
createdAt: Date.now(),
updatedAt: Date.now(),
deletedAt: null
}
//
const practiceIndex = sectionsWithPractice.findIndex(s => s.id === practiceSection.id)
if (practiceIndex !== -1) {
sectionsWithPractice.splice(practiceIndex + 1, 0, discussionSection)
} else {
sectionsWithPractice.push(discussionSection)
}
courseSections.value = sectionsWithPractice
groupedSections.value = groupSectionsByChapter(sectionsWithPractice)
console.log('✅ 设置后的courseSections:', courseSections.value) console.log('✅ 设置后的courseSections:', courseSections.value)
console.log('✅ 设置后的groupedSections:', groupedSections.value) console.log('✅ 设置后的groupedSections:', groupedSections.value)
console.log('✅ groupedSections长度:', groupedSections.value.length) console.log('✅ groupedSections长度:', groupedSections.value.length)
console.log('✅ 已添加练习章节:', practiceSection)
} else { } else {
console.log('❌ API返回的章节数据为空或格式错误') console.log('❌ API返回的章节数据为空或格式错误')
console.log('❌ response.data:', response.data) console.log('❌ response.data:', response.data)
@ -2499,7 +2444,20 @@ const handlePractice = async (section: CourseSection) => {
} }
try { try {
// API //
console.log('👤 获取用户信息...')
const userInfoResponse = await AuthApi.getUserInfo()
if (!userInfoResponse.success || !userInfoResponse.result?.baseInfo?.id) {
console.error('❌ 获取用户信息失败:', userInfoResponse)
message.error('获取用户信息失败,请重新登录')
return
}
const studentId = userInfoResponse.result.baseInfo.id
console.log('✅ 获取用户信息成功学生ID:', studentId)
// API
const response = await CourseApi.getSectionExercise(courseId.value, section.id.toString()) const response = await CourseApi.getSectionExercise(courseId.value, section.id.toString())
if (response.data && (response.data.code === 200 || response.data.code === 0)) { if (response.data && (response.data.code === 200 || response.data.code === 0)) {
@ -2507,21 +2465,120 @@ const handlePractice = async (section: CourseSection) => {
// //
const exerciseData = response.data.result const exerciseData = response.data.result
if (exerciseData && Array.isArray(exerciseData)) { if (exerciseData && Array.isArray(exerciseData) && exerciseData.length > 0) {
// // ID
practiceQuestions.value = exerciseData const firstExam = exerciseData[0]
currentPracticeSection.value = section const examId = firstExam.id
practiceMode.value = true
practiceStarted.value = true //
practiceFinished.value = false
currentQuestionIndex.value = 0
// console.log('🔍 获取到考试信息:', firstExam)
practiceAnswers.value = new Array(exerciseData.length).fill(null).map(() => []) console.log('📋 开始获取考试题目考试ID:', examId, '学生ID:', studentId)
fillAnswers.value = new Array(exerciseData.length).fill(null).map(() => [])
essayAnswers.value = new Array(exerciseData.length).fill('')
console.log('✅ 练习模式已启动,题目数量:', practiceQuestions.value.length) // IDID
const questionsResponse = await CourseApi.getExamQuestions(examId, studentId)
if (questionsResponse.data && (questionsResponse.data.code === 200 || questionsResponse.data.code === 0)) {
console.log('✅ 获取考试题目成功:', questionsResponse.data)
const questionsList = questionsResponse.data.result
if (questionsList && Array.isArray(questionsList) && questionsList.length > 0) {
console.log('📝 题目列表:', questionsList)
// ID
const detailedQuestions = []
for (const questionItem of questionsList) {
try {
console.log('🔍 获取题目详情题目ID:', questionItem.id)
const detailResponse = await CourseApi.getQuestionDetail(questionItem.id)
if (detailResponse.data && (detailResponse.data.code === 200 || detailResponse.data.code === 0)) {
const questionDetail = detailResponse.data.result
console.log('✅ 获取题目详情成功:', questionDetail)
detailedQuestions.push(questionDetail)
} else {
console.warn('⚠️ 获取题目详情失败:', questionItem.id, detailResponse.data?.message)
}
} catch (detailError) {
console.error('❌ 获取题目详情异常:', questionItem.id, detailError)
}
}
if (detailedQuestions.length > 0) {
//
const processedQuestions = detailedQuestions.map((questionData, index) => {
console.log(`🔍 处理题目 ${index + 1}:`, questionData)
// API
const question = questionData.question
const answers = questionData.answer || []
// 0=1=2=3=4=
const typeMap: { [key: number]: string } = {
0: '单选题',
1: '多选题',
2: '判断题',
3: '填空题',
4: '简答题'
}
//
const options = answers
.sort((a: any, b: any) => a.orderNo - b.orderNo) // orderNo
.map((answer: any) => answer.content)
//
const correctAnswers = answers
.filter((answer: any) => answer.izCorrent === 1)
.map((answer: any) => answer.orderNo - 1) // orderNo10
console.log(`📝 题目选项:`, options)
console.log(`✅ 正确答案索引:`, correctAnswers)
// API
const processedQuestion = {
id: question.id,
title: question.content || `题目 ${index + 1}`,
type: typeMap[question.type] || '单选题',
score: question.score || 5,
options: options,
correctAnswer: correctAnswers.length === 1 ? correctAnswers[0] : correctAnswers,
analysis: question.analysis || '',
difficulty: question.difficulty || 0,
//
originalData: questionData
}
console.log(`✅ 处理后的题目 ${index + 1}:`, processedQuestion)
return processedQuestion
})
//
practiceQuestions.value = processedQuestions
currentPracticeSection.value = section
practiceMode.value = true
practiceStarted.value = true //
practiceFinished.value = false
currentQuestionIndex.value = 0
//
practiceAnswers.value = new Array(processedQuestions.length).fill(null).map(() => [])
fillAnswers.value = new Array(processedQuestions.length).fill(null).map(() => [])
essayAnswers.value = new Array(processedQuestions.length).fill('')
console.log('✅ 练习模式已启动,题目数量:', practiceQuestions.value.length)
console.log('✅ 处理后的题目列表:', practiceQuestions.value)
} else {
console.warn('⚠️ 没有获取到有效的题目详情')
message.warning('没有获取到有效的题目详情')
}
} else {
console.warn('⚠️ 考试题目列表为空:', questionsList)
message.warning('考试题目列表为空')
}
} else {
console.error('❌ 获取考试题目失败:', questionsResponse.data?.message || questionsResponse.message)
message.error(questionsResponse.data?.message || questionsResponse.message || '获取考试题目失败')
}
} else { } else {
console.warn('⚠️ 练习数据格式异常:', exerciseData) console.warn('⚠️ 练习数据格式异常:', exerciseData)
message.warning('练习数据格式异常') message.warning('练习数据格式异常')
@ -2791,17 +2848,40 @@ const loadDiscussionData = async (section: CourseSection) => {
// //
const discussionData = response.data.result const discussionData = response.data.result
if (discussionData && Array.isArray(discussionData)) { if (discussionData && Array.isArray(discussionData) && discussionData.length > 0) {
// //
discussionList.value = discussionData.map(comment => ({ const firstDiscussion = discussionData[0]
...comment, console.log('🔍 讨论数据详情:', firstDiscussion)
isLiked: false, //
likes: comment.likes || 0 // if (firstDiscussion.title) {
})) discussionTitle.value = firstDiscussion.title
console.log('✅ 讨论数据加载完成,讨论数量:', discussionList.value.length) console.log('✅ 设置讨论标题:', firstDiscussion.title)
}
if (firstDiscussion.description) {
// HTML
const originalDescription = firstDiscussion.description
const strippedDescription = stripHtmlTags(originalDescription)
discussionDescription.value = strippedDescription
console.log('✅ 原始描述:', originalDescription)
console.log('✅ 处理后描述:', strippedDescription)
}
// API
// API
//
discussionList.value = []
console.log('✅ 讨论主题数据加载完成')
// TODO: API
// : loadDiscussionComments(firstDiscussion.id)
} else { } else {
console.warn('⚠️ 讨论数据格式异常:', discussionData) console.warn('⚠️ 讨论数据格式异常:', discussionData)
discussionList.value = [] discussionList.value = []
//
discussionTitle.value = '讨论'
discussionDescription.value = ''
} }
} else { } else {
console.error('❌ 获取章节讨论失败:', response.data?.message || response.message) console.error('❌ 获取章节讨论失败:', response.data?.message || response.message)
@ -2815,11 +2895,25 @@ const loadDiscussionData = async (section: CourseSection) => {
} }
} }
// HTML
const stripHtmlTags = (html: string): string => {
if (!html) return ''
// DOMHTML
const tempDiv = document.createElement('div')
tempDiv.innerHTML = html
//
return tempDiv.textContent || tempDiv.innerText || ''
}
const exitDiscussion = () => { const exitDiscussion = () => {
console.log('🚪 正在退出讨论模式...') console.log('🚪 正在退出讨论模式...')
discussionMode.value = false discussionMode.value = false
currentDiscussionSection.value = null currentDiscussionSection.value = null
discussionList.value = [] discussionList.value = []
discussionTitle.value = '讨论'
discussionDescription.value = ''
newComment.value = '' newComment.value = ''
replyingTo.value = null replyingTo.value = null
console.log('✅ 已退出讨论模式discussionMode:', discussionMode.value) console.log('✅ 已退出讨论模式discussionMode:', discussionMode.value)

View File

@ -133,9 +133,9 @@ const examName = ref(route.query.examName as string || '考试')
// const viewCount = ref(1024) // // const viewCount = ref(1024) //
// //
const goBack = () => { // const goBack = () => {
router.push(`/course/${courseId.value}`) // router.push(`/course/${courseId.value}`)
} // } // 使