|
|
|
@ -191,6 +191,7 @@ export default { |
|
|
|
touchStartTime: 0, // 触摸开始时间 |
|
|
|
touchStartY: 0, // 触摸开始Y坐标 |
|
|
|
userScrollTimer: null, // 用户滚动检测定时器 |
|
|
|
lastUserScrollTime: 0, // 最后一次用户滚动时间 |
|
|
|
courseIdList: [], |
|
|
|
bookTitle: '', |
|
|
|
courseList: [ |
|
|
|
@ -322,6 +323,9 @@ export default { |
|
|
|
|
|
|
|
// 如果移动距离超过阈值,认为是滚动操作 |
|
|
|
if (deltaY > 10) { |
|
|
|
// 记录用户滚动时间 |
|
|
|
this.lastUserScrollTime = Date.now(); |
|
|
|
|
|
|
|
// 如果当前正在自动滚动,立即停止 |
|
|
|
if (this.isScrolling) { |
|
|
|
console.log('🛑 检测到用户手动滚动,停止自动滚动'); |
|
|
|
@ -345,14 +349,17 @@ export default { |
|
|
|
this.userScrollTimer = setTimeout(() => { |
|
|
|
console.log('✋ 用户滚动操作结束,允许自动滚动'); |
|
|
|
this.userScrollTimer = null; |
|
|
|
}, 1000); // 1秒后允许自动滚动 |
|
|
|
}, 500); // 减少到500ms,提高响应性 |
|
|
|
|
|
|
|
console.log('👆 用户停止触摸屏幕'); |
|
|
|
}, |
|
|
|
|
|
|
|
// 检查是否应该阻止自动滚动 |
|
|
|
shouldPreventAutoScroll() { |
|
|
|
return this.isUserTouching || this.userScrollTimer !== null; |
|
|
|
// 降低敏感度:只有在用户正在触摸且最近有滚动行为时才阻止 |
|
|
|
const now = Date.now(); |
|
|
|
const recentUserScroll = this.userScrollTimer !== null && (now - this.lastUserScrollTime) < 1000; |
|
|
|
return this.isUserTouching && recentUserScroll; |
|
|
|
}, |
|
|
|
|
|
|
|
// 处理scroll-view滚动事件 |
|
|
|
@ -368,9 +375,10 @@ export default { |
|
|
|
if (this.isScrolling) { |
|
|
|
// 如果滚动差异很大,可能是用户手动滚动,中断自动滚动状态 |
|
|
|
const scrollDifference = Math.abs(previousScrollTop - scrollTop); |
|
|
|
if (scrollDifference > 100) { // 大幅度滚动,很可能是手动操作 |
|
|
|
if (scrollDifference > 50) { // 调整阈值,避免误判 |
|
|
|
console.log('🖐️ 检测到手动滚动,中断自动滚动状态'); |
|
|
|
this.isScrolling = false; |
|
|
|
this.lastUserScrollTime = Date.now(); // 记录手动滚动时间 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -728,6 +736,8 @@ export default { |
|
|
|
|
|
|
|
// 处理滚动到高亮文本 |
|
|
|
onScrollToText(scrollData) { |
|
|
|
console.log('📍 收到滚动请求:', scrollData); |
|
|
|
|
|
|
|
// 检查是否应该阻止自动滚动(用户正在手动操作) |
|
|
|
if (this.shouldPreventAutoScroll()) { |
|
|
|
console.log('🚫 用户正在手动滚动,跳过自动滚动到文本'); |
|
|
|
@ -746,7 +756,7 @@ export default { |
|
|
|
return; |
|
|
|
} |
|
|
|
this.performScrollToText(scrollData); |
|
|
|
}, 100); // 100ms防抖延迟 |
|
|
|
}, 50); // 减少防抖延迟,提高响应性 |
|
|
|
}, |
|
|
|
|
|
|
|
// 执行滚动到高亮文本的具体逻辑 |
|
|
|
@ -817,7 +827,52 @@ export default { |
|
|
|
this.isScrolling = true; |
|
|
|
|
|
|
|
// 等待DOM更新后再查找元素 |
|
|
|
this.$nextTick(() => { |
|
|
|
this.$nextTick(async () => { |
|
|
|
try { |
|
|
|
// 获取所有元素的真实位置信息 |
|
|
|
const elementPositions = await this.getAllElementPositions(); |
|
|
|
console.log('📏 获取到的元素位置信息:', elementPositions); |
|
|
|
|
|
|
|
// 计算精确的滚动位置 |
|
|
|
const preciseScrollTop = await this.calculatePreciseScrollPosition(scrollData, elementPositions); |
|
|
|
|
|
|
|
if (preciseScrollTop !== null) { |
|
|
|
// 检查是否需要滚动(避免不必要的滚动) |
|
|
|
const currentScroll = this.scrollTops[this.currentPage - 1] || 0; |
|
|
|
const scrollDifference = Math.abs(preciseScrollTop - currentScroll); |
|
|
|
|
|
|
|
if (scrollDifference > 20) { |
|
|
|
this.$set(this.scrollTops, this.currentPage - 1, preciseScrollTop); |
|
|
|
console.log('✅ 使用精确位置滚动:', { |
|
|
|
selector, |
|
|
|
preciseScrollTop, |
|
|
|
currentPage: this.currentPage, |
|
|
|
scrollDifference |
|
|
|
}); |
|
|
|
|
|
|
|
// 滚动完成后重置状态 |
|
|
|
setTimeout(() => { |
|
|
|
resetScrollingState(); |
|
|
|
}, 200); |
|
|
|
} else { |
|
|
|
resetScrollingState(); |
|
|
|
console.log('📍 目标已在视野内,无需滚动'); |
|
|
|
} |
|
|
|
} else { |
|
|
|
// 精确计算失败,使用原有的查询方法作为备用 |
|
|
|
console.log('🔄 精确计算失败,使用备用查询方法'); |
|
|
|
this.fallbackScrollToText(selector, resetScrollingState, safetyTimeout); |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
console.error('❌ 精确滚动计算失败:', error); |
|
|
|
// 使用原有的查询方法作为备用 |
|
|
|
this.fallbackScrollToText(selector, resetScrollingState, safetyTimeout); |
|
|
|
} |
|
|
|
}); |
|
|
|
}, |
|
|
|
|
|
|
|
// 备用滚动方法(原有的查询方式) |
|
|
|
fallbackScrollToText(selector, resetScrollingState, safetyTimeout) { |
|
|
|
// 使用uni.createSelectorQuery获取元素位置 |
|
|
|
const query = uni.createSelectorQuery().in(this); |
|
|
|
|
|
|
|
@ -855,13 +910,13 @@ export default { |
|
|
|
const currentScroll = this.scrollTops[this.currentPage - 1] || 0; |
|
|
|
const scrollDifference = Math.abs(finalScrollTop - currentScroll); |
|
|
|
|
|
|
|
if (scrollDifference > 30) { // 降低滚动阈值,提高响应性 |
|
|
|
if (scrollDifference > 20) { // 进一步降低滚动阈值,提高响应性 |
|
|
|
this.$set(this.scrollTops, this.currentPage - 1, finalScrollTop); |
|
|
|
|
|
|
|
// 滚动完成后重置状态 |
|
|
|
setTimeout(() => { |
|
|
|
resetScrollingState(); |
|
|
|
}, 300); // 稍微减少等待时间 |
|
|
|
}, 200); // 减少等待时间 |
|
|
|
|
|
|
|
console.log('✅ 滚动到高亮文本:', { |
|
|
|
selector, |
|
|
|
@ -887,18 +942,18 @@ export default { |
|
|
|
// 尝试备用方案:直接滚动到页面顶部附近 |
|
|
|
if (!targetRect) { |
|
|
|
console.log('🔄 尝试备用滚动方案'); |
|
|
|
const fallbackScrollTop = scrollData.highlightIndex * 100; // 简单估算位置 |
|
|
|
this.$set(this.scrollTops, this.currentPage - 1, fallbackScrollTop); |
|
|
|
// 改进备用方案:基于highlightIndex计算更准确的位置 |
|
|
|
const estimatedPosition = this.calculateEstimatedScrollPosition(scrollData.highlightIndex); |
|
|
|
this.$set(this.scrollTops, this.currentPage - 1, estimatedPosition); |
|
|
|
setTimeout(() => { |
|
|
|
resetScrollingState(); |
|
|
|
}, 300); |
|
|
|
}, 200); |
|
|
|
} else { |
|
|
|
// 立即重置状态 |
|
|
|
resetScrollingState(); |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
}); |
|
|
|
}, |
|
|
|
|
|
|
|
// 查找文本元素在页面中的实际索引 |
|
|
|
@ -922,6 +977,141 @@ export default { |
|
|
|
return -1; // 未找到 |
|
|
|
}, |
|
|
|
|
|
|
|
// 获取所有页面元素的位置信息 |
|
|
|
async getAllElementPositions() { |
|
|
|
return new Promise((resolve) => { |
|
|
|
const currentPageData = this.bookPages[this.currentPage - 1]; |
|
|
|
if (!currentPageData || !Array.isArray(currentPageData)) { |
|
|
|
resolve([]); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
const query = uni.createSelectorQuery().in(this); |
|
|
|
const elementPositions = []; |
|
|
|
|
|
|
|
// 获取scroll-container的位置作为基准 |
|
|
|
query.select('.scroll-container').boundingClientRect(); |
|
|
|
|
|
|
|
// 为每个元素添加查询 |
|
|
|
currentPageData.forEach((item, index) => { |
|
|
|
if (item && (item.type === 'text' || item.type === 'image' || item.type === 'video')) { |
|
|
|
if (item.type === 'text') { |
|
|
|
query.select(`#text-${index}`).boundingClientRect(); |
|
|
|
} else if (item.type === 'image') { |
|
|
|
query.select(`.image-container`).boundingClientRect(); |
|
|
|
} else if (item.type === 'video') { |
|
|
|
query.select(`.video-content`).boundingClientRect(); |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
query.exec((res) => { |
|
|
|
const containerRect = res[0]; |
|
|
|
if (!containerRect) { |
|
|
|
resolve([]); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// 处理查询结果 |
|
|
|
let resultIndex = 1; // 跳过第一个容器结果 |
|
|
|
currentPageData.forEach((item, index) => { |
|
|
|
if (item && (item.type === 'text' || item.type === 'image' || item.type === 'video')) { |
|
|
|
const elementRect = res[resultIndex]; |
|
|
|
if (elementRect) { |
|
|
|
elementPositions.push({ |
|
|
|
index: index, |
|
|
|
type: item.type, |
|
|
|
top: elementRect.top - containerRect.top, |
|
|
|
height: elementRect.height, |
|
|
|
bottom: elementRect.top - containerRect.top + elementRect.height |
|
|
|
}); |
|
|
|
} |
|
|
|
resultIndex++; |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
resolve(elementPositions); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}, |
|
|
|
|
|
|
|
// 计算精确的滚动位置 |
|
|
|
async calculatePreciseScrollPosition(scrollData, elementPositions) { |
|
|
|
if (!elementPositions || elementPositions.length === 0) { |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
let targetElementIndex = -1; |
|
|
|
|
|
|
|
if (scrollData.segmentIndex !== undefined) { |
|
|
|
// 分段音频情况 |
|
|
|
targetElementIndex = scrollData.segmentIndex; |
|
|
|
} else if (scrollData.highlightIndex !== undefined) { |
|
|
|
// 普通音频情况,需要找到对应的文本元素 |
|
|
|
targetElementIndex = this.findTextItemIndex(scrollData.highlightIndex); |
|
|
|
} |
|
|
|
|
|
|
|
if (targetElementIndex === -1) { |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
// 查找目标元素的位置信息 |
|
|
|
const targetElement = elementPositions.find(pos => pos.index === targetElementIndex && pos.type === 'text'); |
|
|
|
|
|
|
|
if (!targetElement) { |
|
|
|
console.warn('未找到目标元素位置信息:', targetElementIndex); |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
// 计算滚动位置:让目标元素显示在屏幕上方1/4处 |
|
|
|
const screenHeight = uni.getSystemInfoSync().windowHeight; |
|
|
|
const offsetFromTop = screenHeight * 0.25; |
|
|
|
|
|
|
|
// 目标滚动位置 = 目标元素顶部位置 - 偏移量 |
|
|
|
const targetScrollTop = Math.max(0, targetElement.top - offsetFromTop); |
|
|
|
|
|
|
|
console.log('🎯 精确滚动位置计算:', { |
|
|
|
targetElementIndex, |
|
|
|
targetElement, |
|
|
|
screenHeight, |
|
|
|
offsetFromTop, |
|
|
|
targetScrollTop |
|
|
|
}); |
|
|
|
|
|
|
|
return targetScrollTop; |
|
|
|
}, |
|
|
|
|
|
|
|
// 计算估算的滚动位置(备用方案) |
|
|
|
calculateEstimatedScrollPosition(highlightIndex) { |
|
|
|
const currentPageData = this.bookPages[this.currentPage - 1]; |
|
|
|
if (!currentPageData || !Array.isArray(currentPageData)) { |
|
|
|
return highlightIndex * 80; // 基础估算 |
|
|
|
} |
|
|
|
|
|
|
|
// 基于页面内容计算更准确的位置 |
|
|
|
let estimatedHeight = 0; |
|
|
|
let textCount = 0; |
|
|
|
|
|
|
|
for (let i = 0; i < currentPageData.length && textCount <= highlightIndex; i++) { |
|
|
|
const item = currentPageData[i]; |
|
|
|
if (item && item.type === 'text' && item.content) { |
|
|
|
if (textCount === highlightIndex) { |
|
|
|
break; |
|
|
|
} |
|
|
|
// 根据内容长度估算高度 |
|
|
|
const contentLength = item.content.length; |
|
|
|
estimatedHeight += Math.max(60, contentLength * 1.2); // 基础高度 + 内容长度因子 |
|
|
|
textCount++; |
|
|
|
} else if (item && item.type === 'image') { |
|
|
|
estimatedHeight += 200; // 图片估算高度 |
|
|
|
} else if (item && item.type === 'video') { |
|
|
|
estimatedHeight += 300; // 视频估算高度 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return Math.max(0, estimatedHeight - 100); // 留一些上边距 |
|
|
|
}, |
|
|
|
|
|
|
|
// 获取音色列表 拿第一个做默认的音色id |
|
|
|
async getVoiceList() { |
|
|
|
const voiceRes = await this.$api.music.list() |
|
|
|
|