/** * 数据获取模块 * 负责所有数据获取相关的逻辑 */ import { campApi } from '../../../config/camp_api.js'; import { generateExpandedStatus } from './utils.js'; /** * 顺序调用 purchaseSection 开启多个小节,完成后静默刷新营地数据(用于前一小节完成后自动开启后一小节) */ function openSectionsThenRefresh(context, campId, sectionIds, index, silent) { if (index >= sectionIds.length) { fetchCampData(context, campId, true); return; } var sectionId = sectionIds[index]; campApi.purchaseSection(campId, sectionId) .then(function () { openSectionsThenRefresh(context, campId, sectionIds, index + 1, silent); }) .catch(function () { openSectionsThenRefresh(context, campId, sectionIds, index + 1, silent); }); } /** * 当上一小节已完成、下一小节未购买时,请求后端 canUnlockSection;若允许且免费则加入自动开启列表,最后统一 openSectionsThenRefresh */ function checkAndAutoOpenNextSections(context, campId, courseList, startIndex, toOpen, silent) { if (startIndex >= courseList.length) { if (toOpen.length > 0) { openSectionsThenRefresh(context, campId, toOpen, 0, silent); } return Promise.resolve(); } var prev = courseList[startIndex - 1]; var curr = courseList[startIndex]; if (!prev.is_completed || curr.is_purchased) { return checkAndAutoOpenNextSections(context, campId, courseList, startIndex + 1, toOpen, silent); } return campApi.canUnlockSection(campId, curr.id) .then(function (res) { var ok = res && (res.success === true || res.code === 200); var canUnlock = ok && (res.can_unlock === true); var isFree = (curr.price_fen == null || curr.price_fen === undefined || curr.price_fen <= 0); if (canUnlock && isFree) { toOpen.push(curr.id); } return checkAndAutoOpenNextSections(context, campId, courseList, startIndex + 1, toOpen, silent); }) .catch(function () { return checkAndAutoOpenNextSections(context, campId, courseList, startIndex + 1, toOpen, silent); }); } /** * 为「未购买」或「有时间间隔且非第一节」的小节请求 can_unlock_section,若返回 can_unlock=false 且 unlock_at 则写入 sectionUnlockAtMap 供页面倒计时展示并禁止进入 * 注意:已购买但配置了时间间隔的小节也可能未到解锁时间(如 1 小时未到),需同样请求以展示倒计时并拦截点击 */ function fetchUnlockAtForLockedSections(context, campId, courseList) { var map = {}; var promises = []; for (var i = 0; i < courseList.length; i++) { var course = courseList[i]; var needUnlockCheck = !course.is_purchased || (course.time_interval_type && course.time_interval_value > 0 && (course.section_number || 0) > 1); if (!needUnlockCheck) { continue; } (function (sectionId) { promises.push( campApi.canUnlockSection(campId, sectionId).then(function (res) { var unlockAt = res && (res.unlock_at !== undefined && res.unlock_at !== null) ? parseInt(res.unlock_at, 10) : null; if (unlockAt && !isNaN(unlockAt) && res.can_unlock === false) { map[sectionId] = unlockAt; } }).catch(function () {}) ); })(course.id); } Promise.all(promises).then(function () { if (Object.keys(map).length > 0) { context.setData({ sectionUnlockAtMap: map }); if (typeof context.startUnlockCountdownTimer === 'function') { context.startUnlockCountdownTimer(); } } }); } /** * 轻量级刷新任务状态(已废弃) * 注意:此函数已不再使用,因为聚合接口 camp_detail_with_user_status 已经返回了完整的任务状态 * 如果需要更新任务状态,请使用 fetchCampData 重新加载完整数据 * @deprecated 不再使用单独的 progress/list 接口 */ export function refreshTaskStatus(context) { // 此函数已废弃,不再执行任何操作 // 如果需要刷新任务状态,请调用 fetchCampData 重新加载完整数据 } /** * 获取所有营地数据(仅在首次加载时使用) * @param {Object} context - 页面上下文 * @param {String} campId - 打卡营ID * @param {Boolean} silent - 是否静默更新(不显示 loading) */ export function fetchCampData(context, campId, silent) { if (!campId) { wx.showToast({ title: '营地ID不能为空', icon: 'none' }); return; } if (silent !== true) { context.setData({ loading: true }); } var userId = wx.getStorageSync('wxuserid') || ''; // 使用新的聚合接口 campApi.getCampDetailWithUserStatus(campId, userId) .then(function (res) { if (!res || res.success !== true) { throw new Error(res && res.message || '获取数据失败'); } var campDetail = res.camp_detail || {}; var camp = campDetail.camp || {}; var userCamp = campDetail.user_camp || {}; var sections = res.sections || []; // 处理介绍类型和内容 var introTypeNormalized = (function(raw) { if (raw === null || raw === undefined) return 'INTRO_TYPE_NONE'; if (typeof raw === 'number') { if (raw === 2) return 'INTRO_TYPE_VIDEO'; if (raw === 1) return 'INTRO_TYPE_IMAGE_TEXT'; return 'INTRO_TYPE_NONE'; } var str = String(raw).toUpperCase(); if (str === '2' || str.indexOf('VIDEO') !== -1) return 'INTRO_TYPE_VIDEO'; if (str === '1' || str.indexOf('IMAGE_TEXT') !== -1 || str.indexOf('IMAGETEXT') !== -1) return 'INTRO_TYPE_IMAGE_TEXT'; return 'INTRO_TYPE_NONE'; })(camp.intro_type); var introContent = camp.intro_content || ''; var isVideo = introTypeNormalized === 'INTRO_TYPE_VIDEO' && introContent; // 获取当前学习的小节ID var currentSectionId = null; if (userCamp.is_joined === true && userCamp.current_section_id) { currentSectionId = String(userCamp.current_section_id); } // 转换打卡营状态 var campStatus = 'NotStarted'; if (userCamp.camp_status) { var statusStr = String(userCamp.camp_status).toUpperCase(); if (statusStr.indexOf('COMPLETED') !== -1) { campStatus = 'Completed'; } else if (statusStr.indexOf('IN_PROGRESS') !== -1 || statusStr.indexOf('INPROGRESS') !== -1) { campStatus = 'InProgress'; } } // 构建访问列表(从 sections 中提取已购买的小节) var accessList = []; sections.forEach(function (sec) { if (sec.is_purchased === true) { accessList.push({ section_id: sec.id }); } }); // 处理小节列表(sections 已经包含任务列表和进度信息) var courseList = sections.map(function (sec) { var tasks = sec.tasks || []; // 处理每个任务的状态,确保审核被拒绝的任务正确显示 tasks.forEach(function(task) { if (!task) return; // 兼容后端解锁关系:若有 prerequisite_task_id 但无 prerequisites,则生成数组供点击逻辑使用 if ((!task.prerequisites || task.prerequisites.length === 0) && task.prerequisite_task_id) { task.prerequisites = [task.prerequisite_task_id]; } if (!task.prerequisites) { task.prerequisites = []; } // 判断任务类型(只有主观题和申论题需要审核) var taskType = String(task.task_type || '').toLowerCase(); var isReviewableTask = taskType === 'subjective' || taskType === 'essay' || task.need_review === true; // 只有需要审核的任务才检查 review_status if (isReviewableTask && task.progress && task.progress.review_status) { var reviewStatus = task.progress.review_status; var reviewStatusUpper = String(reviewStatus).toUpperCase(); // 如果审核状态是 rejected,无论其他状态如何,都应该显示为 Rejected if (reviewStatusUpper === 'REJECTED' || reviewStatus === 'rejected' || reviewStatusUpper === 'REVIEW_STATUS_REJECTED') { task.status = 'Rejected'; } else if (reviewStatusUpper === 'APPROVED' || reviewStatus === 'approved' || reviewStatusUpper === 'REVIEW_STATUS_APPROVED') { // 审核通过,如果已完成则显示 Completed,否则显示 InProgress if (task.progress.is_completed === true || task.progress.is_completed === 1) { task.status = 'Completed'; } else { task.status = 'InProgress'; } } else if (reviewStatusUpper === 'PENDING' || reviewStatus === 'pending' || reviewStatusUpper === 'REVIEW_STATUS_PENDING') { // 待审核状态 if (task.progress.is_completed === true || task.progress.is_completed === 1) { task.status = 'Reviewing'; } else { task.status = 'InProgress'; } } } // 规范化状态值(确保大小写正确) if (task.status) { var normalizedStatus = String(task.status).trim(); if (normalizedStatus === 'NotStarted' || normalizedStatus === 'InProgress' || normalizedStatus === 'Completed' || normalizedStatus === 'Reviewing' || normalizedStatus === 'Rejected') { task.status = normalizedStatus; } else { // 如果状态值不在预期范围内,尝试转换 var statusUpper = normalizedStatus.toUpperCase(); if (statusUpper.indexOf('COMPLETED') !== -1) { task.status = 'Completed'; } else if (statusUpper.indexOf('REVIEWING') !== -1) { task.status = 'Reviewing'; } else if (statusUpper.indexOf('REJECTED') !== -1) { task.status = 'Rejected'; } else if (statusUpper.indexOf('INPROGRESS') !== -1 || statusUpper.indexOf('IN_PROGRESS') !== -1) { task.status = 'InProgress'; } else if (statusUpper.indexOf('NOTSTARTED') !== -1 || statusUpper.indexOf('NOT_STARTED') !== -1) { task.status = 'NotStarted'; } else { // 无法识别,默认为 NotStarted task.status = 'NotStarted'; } } } else { // 如果没有 status 字段,默认为 NotStarted task.status = 'NotStarted'; } }); // 根据实际的 tasks 数组重新计算 section_progress,确保数据正确 var totalTasks = tasks.length; var completedTasks = 0; // 统计已完成的任务数(状态为 Completed 的任务) tasks.forEach(function(task) { if (task.status === 'Completed') { completedTasks++; } }); // 重新计算 section_progress var sectionProgress = { section_id: sec.id, total_tasks: totalTasks, completed_tasks: completedTasks, is_completed: totalTasks > 0 && completedTasks === totalTasks }; // 如果后端返回的 is_completed 状态与计算的不一致,使用计算的结果 var isCompleted = sectionProgress.is_completed; return { id: sec.id, title: sec.title, section_number: sec.section_number, price_fen: sec.price_fen, is_purchased: sec.is_purchased || false, is_started: sec.is_started || false, is_completed: isCompleted, is_current: sec.is_current || false, require_previous_section: sec.require_previous_section || false, time_interval_type: sec.time_interval_type, time_interval_value: sec.time_interval_value, tasks: tasks, section_progress: sectionProgress }; }); // 查找并维护当前小节信息(优先使用 is_current,其次使用 current_section_id) var currentSection = null; if (courseList && courseList.length > 0) { // 优先通过 is_current 查找 currentSection = courseList.find(function (sec) { return sec.is_current === true; }); // 如果通过 is_current 找不到,则通过 current_section_id 查找 if (!currentSection && currentSectionId) { currentSection = courseList.find(function (sec) { return String(sec.id) === String(currentSectionId) || String(sec.section_id) === String(currentSectionId) || String(sec.course_id) === String(currentSectionId); }); } if (currentSection) { } } // 检查当前是否正在播放任务视频 // 如果正在播放任务视频,保留视频播放状态,不要重置 var currentPlayingTaskId = context.data.currentPlayingTaskId; var currentPlayingTaskType = context.data.currentPlayingTaskType; var isPlayingTaskVideo = currentPlayingTaskId && currentPlayingTaskType === 'TASK_TYPE_VIDEO'; var shouldShowVideo = isVideo || (isPlayingTaskVideo && context.data.showVideo === true); // 构建返回数据 var newData = { loading: silent === true ? context.data.loading : false, hasJoined: userCamp.is_joined || false, showVideo: shouldShowVideo, topContent: isPlayingTaskVideo ? context.data.topContent : introContent, backTopContent: introContent, topContentType: isPlayingTaskVideo ? context.data.topContentType : introTypeNormalized, introType: introTypeNormalized, campStatus: campStatus, currentSectionId: currentSectionId, currentSection: currentSection, // 维护当前小节信息 campInfo: { title: camp.title || '营地详情', desc: camp.description || '', cover_image: camp.cover_image || '' }, courseList: courseList, accessList: accessList, hasLogged10Percent: isPlayingTaskVideo ? context.data.hasLogged10Percent : false, sectionUnlockAtMap: {}, sectionUnlockTexts: [] }; // 如果正在播放任务视频,保留当前的 bannerVideoUrl if (isPlayingTaskVideo && context.data.bannerVideoUrl) { newData.bannerVideoUrl = context.data.bannerVideoUrl; } else if (isVideo) { newData.bannerVideoUrl = introContent; } else { newData.bannerVideoUrl = ''; } // 如果正在播放任务视频,保留视频相关的其他状态 if (isPlayingTaskVideo) { newData.currentPlayingTaskId = context.data.currentPlayingTaskId; newData.currentPlayingTaskType = context.data.currentPlayingTaskType; newData.videoTitle = context.data.videoTitle; newData.currentVideoCompletionPercent = context.data.currentVideoCompletionPercent; newData.videoCompletionMap = context.data.videoCompletionMap || {}; newData.isAutoPlay = context.data.isAutoPlay; newData._pendingVideoPlay = context.data._pendingVideoPlay; } // 一次性更新所有数据 context.setData(newData); // 标记数据已加载 context.setData({ _isDataLoaded: true }); // 生成展开状态 if (courseList.length > 0) { generateExpandedStatus(context); } // 未购买的小节请求 can_unlock_section,若有 unlock_at 则用于小节倒计时 if (userCamp.is_joined === true && courseList && courseList.length > 0) { fetchUnlockAtForLockedSections(context, campId, courseList); } // 当小节内最后一个任务完成时,后端会在 progress/update 时自动检查并开启下一小节(若无限制且免费); // 此处作为兜底:拉取营地数据后若发现上一节已完成、下一节未购买且可解锁免费,则再请求开启 if (userCamp.is_joined === true && courseList && courseList.length > 1) { checkAndAutoOpenNextSections(context, campId, courseList, 1, [], silent); } }) .catch(function (error) { wx.showToast({ title: error && error.message || '获取数据失败', icon: 'none' }); context.setData({ loading: false }); }); }