duidui_mini_program/pages/camp_task_objective_questions_result/index.js
2026-03-27 10:41:46 +08:00

670 lines
27 KiB
JavaScript
Raw Permalink 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.

import * as resultService from './modules/result-service.js';
import { questionApi } from '../../config/question_api.js';
import { campApi } from '../../config/camp_api.js';
// 引入 TWEEN已注释改用简单动画
// const TWEEN = require('tween.js');
Page({
data: {
paperId: null,
taskId: null,
paperInfo: {
title: '',
description: '',
duration_minutes: 0
},
questions: [],
currentQuestionIndex: 0,
loading: true,
answers: [],
// 材料相关(与答题页保持一致的双层布局)
materials: [],
currentQuestionMaterial: null,
materialScrollHeight: 400,
currentMaterialIndex: 0,
// 面板拖拽相关
swiperHeight: 0,
panelY: 0,
panelHeight: 400,
panelYMin: 0,
panelYMax: 400,
movableAreaHeight: 600,
showFullMaterial: false,
materialHeight: 400,
isResizing: false,
startY: 0,
startHeight: 500,
lastHeight: 500,
pauseStartTime: null,
currentAnswers: [],
isMultiple: false,
optionMarkers: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'],
optionsClasses: [],
remainingTime: 0,
pausedTime: null,
elapsedSeconds: 0,
tweenHeight: 500,
materialStyle: 'transition: none;',
tagStyle: {
img: 'max-width: 100% !important; width: auto !important; height: auto !important; display: block !important; margin: 20rpx auto !important;',
'img.inline': 'display: inline-block !important; vertical-align: middle !important; max-height: 1.5em !important; width: auto !important; margin: 0 4rpx !important;',
p: 'margin: 0 !important; padding: 0 !important; max-width: 100% !important; box-sizing: border-box !important;',
table: 'border-collapse: collapse !important; margin: 0 !important; max-width: 100% !important; box-sizing: border-box !important;',
th: 'border: 1px solid #ccc !important; padding: 4px 8px !important; max-width: 100% !important; box-sizing: border-box !important;',
td: 'border: 1px solid #ccc !important; padding: 4px 8px !important; max-width: 100% !important; box-sizing: border-box !important;',
},
totalPausedTime: 0,
questionAnsweredStatus: [],
courseId: null,
showExplanation: false,
currentExplanation: '',
showDetail: false,
correctCount: 0,
totalCount: 0,
correctRate: 0,
usedTime: '00:00',
stats: {},
domain: 'https://your-domain.com',
},
onLoad(options) {
const { paper_id, task_id, course_id, task_title, camp_id } = options;
const decodedTitle = task_title ? decodeURIComponent(task_title) : '';
wx.setNavigationBarTitle({
title: decodedTitle || '答题结果'
});
if (!paper_id) {
wx.showToast({
title: '试卷ID不存在',
icon: 'none'
});
return;
}
// 计算主内容区高度、问题面板高度、拖动边界(与答题页一致)
try {
const sys = wx.getSystemInfoSync();
const w = sys.windowWidth || 375;
const h = Math.max(0, sys.windowHeight);
const materialHeaderPx = Math.round(160 * (w / 750));
const panelYMin = materialHeaderPx;
const panelHeight = Math.max(200, h - panelYMin);
const panelYMax = Math.max(panelYMin, h - 80);
const movableAreaHeight = panelYMax + panelHeight;
const defaultPanelY = Math.max(panelYMin, Math.min(panelYMax, Math.round(h / 2)));
this.setData({
swiperHeight: h,
panelHeight,
panelY: defaultPanelY,
panelYMin,
panelYMax,
movableAreaHeight
});
} catch (e) {}
this.setData({
campId: camp_id,
paperId: paper_id,
taskId: task_id,
taskTitle: decodedTitle || '答题结果',
courseId: course_id,
startTime: new Date().getTime(),
totalPausedTime: 0,
pauseStartTime: null,
timerText: '00:00:00',
loading: true
});
// 添加错误处理
this.fetchQuestionPaper(paper_id)
.then(() => {
// 延迟设置 loading 为 false确保加载动画有足够时间显示
setTimeout(() => {
this.setData({ loading: false });
}, 500);
})
.catch(error => {
console.error('加载试卷失败:', error);
wx.showToast({
title: '加载试卷失败',
icon: 'none'
});
this.setData({ loading: false });
});
},
// 获取试卷及问题数据
fetchQuestionPaper(paperId) {
this.setData({ loading: true });
const userId = wx.getStorageSync('wxuserid');
if (!userId) {
wx.showToast({
title: '请先登录',
icon: 'none'
});
return Promise.reject(new Error('用户未登录'));
}
return resultService.fetchQuestionPaper(paperId, this.data.taskId, userId).then(resultData => {
const isQualified = resultData.stats.correctRate >= 60;
// 若无答题记录(如刚重置),应进入答题页而非结果页
const answeredCount = resultData.stats.answeredCount != null ? resultData.stats.answeredCount : resultData.stats.totalCount;
if (answeredCount === 0) {
const q = [
'paper_id=' + encodeURIComponent(this.data.paperId || paperId),
'task_id=' + encodeURIComponent(this.data.taskId || ''),
'camp_id=' + encodeURIComponent(this.data.campId || ''),
'task_title=' + encodeURIComponent(this.data.taskTitle || ''),
'course_id=' + encodeURIComponent(this.data.courseId || '')
].filter(Boolean).join('&');
wx.redirectTo({
url: `/pages/camp_task_objective_questions/index?${q}`,
fail: () => {
this.setData({
paperInfo: resultData.paperInfo,
questions: resultData.questions,
currentQuestionIndex: 0,
correctCount: 0,
totalCount: 0,
correctRate: '0',
usedTime: '00:00',
isQualified: false,
loading: false
});
}
});
return resultData;
}
// 处理 paper 级别的材料(与答题页保持一致)
const materials = resultData.materials || [];
const h = this.data.swiperHeight || 400;
const w = (() => { try { return wx.getSystemInfoSync().windowWidth; } catch (e) { return 375; } })();
const titleBarPx = materials.length > 1
? Math.round(140 * (w / 750))
: Math.round(100 * (w / 750));
const materialScrollHeight = materials.length > 0 ? Math.max(0, h - titleBarPx) : h;
const panelYMin = this.data.panelYMin ?? Math.round(160 * (375 / 750));
const panelHeight = Math.max(200, h - panelYMin);
const panelYMax = Math.max(panelYMin, h - 80);
const movableAreaHeight = panelYMax + panelHeight;
const defaultPanelY = Math.max(panelYMin, Math.min(panelYMax, Math.round(h / 2)));
// 根据第一道题目的 material_id 查找对应材料
const firstQuestion = resultData.questions[0] || null;
const currentQuestionMaterial = this.findMaterialForQuestion(firstQuestion, materials);
const actualPanelY = currentQuestionMaterial ? defaultPanelY : 0;
this.setData({
paperInfo: resultData.paperInfo,
questions: resultData.questions,
currentQuestionIndex: 0,
correctCount: resultData.stats.correctCount,
totalCount: resultData.stats.totalCount,
correctRate: resultData.stats.correctRate.toFixed(2),
usedTime: resultData.stats.usedTime,
isQualified: isQualified,
materials,
materialScrollHeight,
currentQuestionMaterial,
panelHeight,
panelY: actualPanelY,
panelYMin,
panelYMax,
movableAreaHeight,
loading: false
});
return resultData;
}).catch(err => {
console.error('API调用失败:', err);
this.setData({ loading: false });
throw err;
});
},
/**
* 根据题目的 material_id 查找对应的材料(与答题页一致)
*/
findMaterialForQuestion(question, materials) {
const mats = materials || this.data.materials || [];
if (!question || !question.material_id) return null;
return mats.find(m => m.id === question.material_id) || null;
},
/**
* 切换题目时更新当前题目的材料和面板位置
*/
updateQuestionMaterial(questionIndex) {
const question = this.data.questions[questionIndex];
const currentQuestionMaterial = this.findMaterialForQuestion(question);
const { panelYMin, panelYMax, swiperHeight } = this.data;
const defaultPanelY = Math.max(panelYMin, Math.min(panelYMax, Math.round(swiperHeight / 2)));
const newPanelY = currentQuestionMaterial ? defaultPanelY : 0;
this.setData({
currentQuestionIndex: questionIndex,
currentQuestionMaterial,
panelY: newPanelY
});
},
// 面板拖拽相关方法(与答题页一致)
onPanelDragStart(e) {
if (!e.touches || !e.touches.length) return;
this._panelDragStartY = e.touches[0].clientY;
this._panelDragStartPanelY = this.data.panelY;
},
onPanelDragMove(e) {
if (!e.touches || !e.touches.length || this._panelDragStartY == null) return;
const { panelYMin, panelYMax } = this.data;
const deltaY = e.touches[0].clientY - this._panelDragStartY;
let newY = this._panelDragStartPanelY + deltaY;
newY = Math.max(panelYMin, Math.min(panelYMax, newY));
this.setData({ panelY: newY });
},
onPanelDragEnd(e) {
this._panelDragStartY = null;
this._panelDragStartPanelY = null;
},
// 移除HTML标签的辅助方法
stripHtmlTags(html) {
if (!html) return '';
return html.replace(/<\/?[^>]+(>|$)/g, "");
},
// 材料切换事件
onMaterialChange(e) {
this.setData({
currentMaterialIndex: e.detail.current
});
},
// 更新选项样式
updateOptionsClasses() {
const { questions, answers } = this.data;
const newOptionsClasses = questions.map((question, qIndex) => {
const questionAnswers = answers[qIndex] || [];
return question.options.map((_, optIndex) =>
questionAnswers.includes(optIndex) ? 'option-selected-single' : ''
);
});
this.setData({
optionsClasses: newOptionsClasses
});
},
// 获取选项样式
getOptionClass(optionIndex) {
const { currentQuestionIndex, questions, answers } = this.data;
if (!answers[currentQuestionIndex]) return '';
const currentQuestion = questions[currentQuestionIndex];
const currentAnswers = answers[currentQuestionIndex];
if (currentAnswers.includes(optionIndex)) {
return currentQuestion.type === 'single' ? 'option-selected-single' : 'option-selected-multiple';
}
return '';
},
// 题目切换动画开始时
onQuestionTransition() {
// 保持为空
},
// 题目切换动画完成时
onQuestionAnimationFinish(e) {
const index = e.detail.current;
this.updateQuestionMaterial(index);
},
// 修改拖动相关的方法
startResize(e) {
const currentQuestion = this.data.questions[this.data.currentQuestionIndex];
if (!currentQuestion?.materials?.length) return;
// 停止之前的动画(已注释 TWEEN改用简单清理
// if (this.tween) {
// this.tween.stop();
// this.tween = null;
// }
if (this.animationTimer) {
clearTimeout(this.animationTimer);
this.animationTimer = null;
}
this.initialY = e.touches[0].clientY;
this.initialHeight = this.data.materialHeight;
if (!this.systemInfo) {
this.systemInfo = wx.getSystemInfoSync();
this.rpxRatio = 750 / this.systemInfo.windowWidth;
}
this.setData({
isResizing: true,
materialStyle: 'transition: none;'
});
},
onResize(e) {
if (!this.data.isResizing) return;
if (!this.resizeAnimationFrame) {
this.resizeAnimationFrame = setTimeout(() => {
const deltaY = e.touches[0].clientY - this.initialY;
const newHeight = this.initialHeight + deltaY * this.rpxRatio;
// 限制高度范围
const minHeight = 200;
const maxHeight = 800;
const clampedHeight = Math.min(Math.max(newHeight, minHeight), maxHeight);
this.setData({
materialHeight: clampedHeight,
materialStyle: 'transition: none;'
});
this.resizeAnimationFrame = null;
}, 16);
}
},
endResize() {
if (!this.data.isResizing) return;
// 清理 requestAnimationFrame
if (this.resizeAnimationFrame) {
clearTimeout(this.resizeAnimationFrame);
this.resizeAnimationFrame = null;
}
const currentHeight = this.data.materialHeight;
const targetHeight = Math.round(currentHeight / 50) * 50;
// 使用简单动画替代 TWEEN
this.setData({
materialHeight: targetHeight,
materialStyle: 'transition: height 0.3s ease-out;',
isResizing: false
});
// 动画完成后清理
setTimeout(() => {
this.setData({
materialStyle: '' // 恢复默认样式
});
this.initialY = null;
this.initialHeight = null;
this.tween = null;
}, 300);
},
// 在页面卸载时清理所有定时器和状态
onUnload() {
// if (this.tween) {
// this.tween.stop();
// this.tween = null;
// }
if (this.animationTimer) {
clearTimeout(this.animationTimer);
this.animationTimer = null;
}
if (this.resizeAnimationFrame) {
clearTimeout(this.resizeAnimationFrame);
this.resizeAnimationFrame = null;
}
this.initialY = null;
this.initialHeight = null;
// if (this.timerInterval) {
// clearInterval(this.timerInterval);
// this.timerInterval = null;
// }
},
// 修改富文本点击处理方法
onRichTextTap(e) {
const nodes = e.currentTarget.dataset.content;
if (!nodes || !Array.isArray(nodes)) return;
// 收集所有图片URL
const images = nodes
.filter(node => node.name === 'img')
.map(node => node.attrs.src)
.filter(url => url && url.startsWith('http'));
// 如果找到图片,则打开预览
if (images.length > 0) {
wx.previewImage({
current: images[0],
urls: images
});
}
},
// 添加单独的图片预览方法
previewImage(e) {
const { url, urls } = e.currentTarget.dataset;
wx.previewImage({
current: url,
urls: urls || [url]
});
},
// 如果需要处理材料中的图片预览,也可以添加一个专门的方法
onMaterialImageTap(e) {
const url = e.currentTarget.dataset.url;
if (url) {
wx.previewImage({
current: url,
urls: [url]
});
}
},
// 修改富文本处理方法
processRichText(content) {
if (!content) return '';
// 为行内图片添加 class
return content.replace(/<img(.*?)>/g, (match, attrs) => {
// 检查图片是否在段落中且没有换行符
const isInline = match.includes('style="display: inline') ||
match.includes('vertical-align: middle');
if (isInline) {
// 如果是行内图片,添加 inline class
return match.replace('<img', '<img class="inline"');
}
return match;
});
},
// 图片点击事件处理
onImgTap(e) {
const { src } = e.detail;
wx.previewImage({
current: src,
urls: [src]
});
},
onQuestionChange(e) {
const index = e.detail.current;
this.updateQuestionMaterial(index);
},
// 从答题卡跳转到指定题目
jumpToQuestion(e) {
const index = parseInt(e.currentTarget.dataset.index) || 0;
this.setData({ showDetail: true }, () => {
this.updateQuestionMaterial(index);
});
},
// 添加 nextQuestion 方法(如果还没有的话)
nextQuestion: function () {
if (this.data.currentQuestionIndex < this.data.questions.length - 1) {
this.setData({
currentQuestionIndex: this.data.currentQuestionIndex + 1
});
}
},
// 添加数据变化监听器
onAnswersChange: function () {
console.log('Current answers state:', this.data.answers);
},
// 添加一个方法用于判断选项是否被选中
isOptionSelected: function (questionIndex, optionIndex) {
const answers = this.data.answers[questionIndex] || [];
return answers.indexOf(optionIndex) !== -1;
},
// 修改判断题目是否已答的方法
isQuestionAnswered: function (index) {
// 获取当前题目的答案数组
const answers = this.data.answers;
console.log('answers:', answers);
if (!answers || !answers[index]) return false;
// 直接遍历答案数组,检查是否有 true 值
return answers[index].some(answer => answer === true);
},
// 监听材料区域高度变化
onMaterialResize(height) {
// height 参数是rpx值需要转换为px
const systemInfo = wx.getSystemInfoSync();
const pxHeight = height * systemInfo.windowWidth / 750;
this.setData({
materialHeight: pxHeight
});
},
// 在拖动手柄时实时更新高度
onResizeMove(e) {
// ... 现有的拖动逻辑 ...
const newHeight = this.calculateNewHeight(e);
this.onMaterialResize(newHeight);
},
// 拖动结束时保存最终高度
onResizeEnd(e) {
// ... 现有的结束拖动逻辑 ...
const finalHeight = this.calculateNewHeight(e);
this.onMaterialResize(finalHeight);
},
// 重新答题
retakeQuiz() {
wx.showModal({
title: '提示',
content: '重新答题会清空当前答题记录,是否继续?',
success: (res) => {
if (res.confirm) {
// 获取用户ID
const userId = wx.getStorageSync('wxuserid');
if (!userId) {
wx.showToast({
title: '请先登录',
icon: 'none'
});
return;
}
// 显示加载提示
wx.showLoading({
title: '处理中...',
mask: true
});
// 调用重置任务进度接口
campApi.resetTaskProgress({
user_id: String(userId),
task_id: String(this.data.taskId)
})
.then(resetRes => {
console.log('重置任务进度接口返回:', resetRes);
// 判断重置是否成功(兼容多种返回格式)
const isSuccess = resetRes.success === true ||
resetRes.code === 200 ||
(resetRes.data && resetRes.data.success === true);
if (isSuccess) {
wx.hideLoading();
wx.showToast({
title: '已清空答题记录',
icon: 'success',
duration: 1500
});
// 延迟跳转,确保提示显示
setTimeout(() => {
// 构建跳转参数
const params = {
paper_id: this.data.paperId,
task_id: this.data.taskId,
course_id: this.data.courseId,
task_title: this.data.taskTitle,
camp_id: this.data.campId
};
const queryString = Object.keys(params)
.map(key => `${key}=${encodeURIComponent(params[key] || '')}`)
.join('&');
// 跳转到答题页面
wx.redirectTo({
url: `/pages/camp_task_objective_questions/index?${queryString}`
});
}, 1500);
} else {
wx.hideLoading();
wx.showToast({
title: resetRes.message || resetRes.msg || '重置失败',
icon: 'none'
});
}
})
.catch(err => {
console.error('重置任务进度失败:', err);
wx.hideLoading();
wx.showToast({
title: '重置失败,请重试',
icon: 'none'
});
});
}
}
});
},
// 查看结算
showSummary() {
this.setData({
showDetail: false
});
},
// 查看全部解析
showAnalysis() {
this.setData({ showDetail: true }, () => {
this.updateQuestionMaterial(0);
});
}
});