398 lines
15 KiB
JavaScript
398 lines
15 KiB
JavaScript
/**
|
||
* 数据获取模块
|
||
* 负责所有数据获取相关的逻辑
|
||
*/
|
||
|
||
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 });
|
||
});
|
||
}
|
||
|