|
|
@ -20,7 +20,9 @@ |
|
|
<swiper class="content-swiper" :current="currentPage - 1" @change="onSwiperChange"> |
|
|
<swiper class="content-swiper" :current="currentPage - 1" @change="onSwiperChange"> |
|
|
<swiper-item v-for="(page, index) in bookPages" :key="index" class="swiper-item"> |
|
|
<swiper-item v-for="(page, index) in bookPages" :key="index" class="swiper-item"> |
|
|
<scroll-view scroll-y :scroll-top="scrollTops[index] || 0" :scroll-with-animation="true" |
|
|
<scroll-view scroll-y :scroll-top="scrollTops[index] || 0" :scroll-with-animation="true" |
|
|
style="height: 100vh;" class="scroll-container" @scroll="onScroll" @touchstart="onTouchStart" |
|
|
|
|
|
|
|
|
style="height: 100vh;" |
|
|
|
|
|
:id="`scroll-container-${index}`" |
|
|
|
|
|
class="scroll-container" @scroll="onScroll" @touchstart="onTouchStart" |
|
|
@touchmove="onTouchMove" @touchend="onTouchEnd"> |
|
|
@touchmove="onTouchMove" @touchend="onTouchEnd"> |
|
|
<view class="content-area" @click="toggleNavbar"> |
|
|
<view class="content-area" @click="toggleNavbar"> |
|
|
<view class="title">{{ currentPageTitle }}</view> |
|
|
<view class="title">{{ currentPageTitle }}</view> |
|
|
@ -40,19 +42,10 @@ |
|
|
<text class="card-line-text">划线重点</text> |
|
|
<text class="card-line-text">划线重点</text> |
|
|
</view> |
|
|
</view> |
|
|
<view v-for="(item, itemIndex) in page" :key="itemIndex" class="text-content"> |
|
|
<view v-for="(item, itemIndex) in page" :key="itemIndex" class="text-content"> |
|
|
<image class="card-image" v-if="item && item.type === 'image'" :src="item.imageUrl" |
|
|
|
|
|
|
|
|
<image class="card-image" |
|
|
|
|
|
:id="`image-${itemIndex}`" |
|
|
|
|
|
v-if="item && item.type === 'image'" :src="item.imageUrl" |
|
|
mode="widthFix"></image> |
|
|
mode="widthFix"></image> |
|
|
<!-- <view :class="['english-text-container', 'clickable-text', { 'lead-text': isCardTextHighlighted(page, itemIndex) }]" v-else-if="item && item.type === 'text' && item.language === 'en' && item.content" @click.stop="handleTextClick(item.content, item, index)" > |
|
|
|
|
|
<text |
|
|
|
|
|
v-for="(token, tokenIndex) in splitEnglishSentence(item.content)" |
|
|
|
|
|
:key="tokenIndex" |
|
|
|
|
|
:class="['english-token', { 'clickable-word': token.isWord && findWordDefinition(token.text) }]" |
|
|
|
|
|
@click.stop="token.isWord && findWordDefinition(token.text) ? handleWordClick(token.text) : null" |
|
|
|
|
|
user-select |
|
|
|
|
|
:style="item.style" |
|
|
|
|
|
>{{ token.text }}</text> |
|
|
|
|
|
</view> --> |
|
|
|
|
|
<!-- <view :class="{ 'lead-text': isCardTextHighlighted(page, itemIndex) }" v-else-if="item && item.type === 'text' && item.language === 'zh' && item.content" @click.stop="handleTextClick(item.content, item, index)"> --> |
|
|
|
|
|
<view :class="{ |
|
|
<view :class="{ |
|
|
'lead-text': isCardTextHighlighted(page, itemIndex), |
|
|
'lead-text': isCardTextHighlighted(page, itemIndex), |
|
|
'introduction-text' : item.isLead, |
|
|
'introduction-text' : item.isLead, |
|
|
@ -89,12 +82,12 @@ |
|
|
|
|
|
|
|
|
<!-- 图片页面 --> |
|
|
<!-- 图片页面 --> |
|
|
<view v-else-if="item.type === 'image'" class="image-container" |
|
|
<view v-else-if="item.type === 'image'" class="image-container" |
|
|
:ref="`imageRef_${index}_${itemIndex}`"> |
|
|
|
|
|
|
|
|
:ref="`imageRef_${index}_${itemIndex}`" :id="`image-${itemIndex}`"> |
|
|
<image class="content-image" :src="item.imageUrl" mode="widthFix"></image> |
|
|
<image class="content-image" :src="item.imageUrl" mode="widthFix"></image> |
|
|
</view> |
|
|
</view> |
|
|
|
|
|
|
|
|
<!-- 视频页面 --> |
|
|
<!-- 视频页面 --> |
|
|
<view v-else-if="item.type === 'video'" class="video-content" @click.stop> |
|
|
|
|
|
|
|
|
<view v-else-if="item.type === 'video'" class="video-content" :id="`video-${itemIndex}`" @click.stop> |
|
|
<!-- 视频加载状态 --> |
|
|
<!-- 视频加载状态 --> |
|
|
<view v-if="videoLoading" class="video-loading"> |
|
|
<view v-if="videoLoading" class="video-loading"> |
|
|
<text class="loading-text">视频加载中...</text> |
|
|
<text class="loading-text">视频加载中...</text> |
|
|
@ -795,7 +788,7 @@ export default { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
console.log('开始滚动到文本:', { selector, scrollData }); |
|
|
|
|
|
|
|
|
//console.log('开始滚动到文本:', { selector, scrollData }); |
|
|
|
|
|
|
|
|
// 标记正在滚动 |
|
|
// 标记正在滚动 |
|
|
this.isScrolling = true; |
|
|
this.isScrolling = true; |
|
|
@ -954,14 +947,15 @@ export default { |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
// 获取所有页面元素的位置信息 |
|
|
// 获取所有页面元素的位置信息 |
|
|
|
|
|
/* |
|
|
async getAllElementPositions() { |
|
|
async getAllElementPositions() { |
|
|
return new Promise((resolve) => { |
|
|
return new Promise((resolve) => { |
|
|
|
|
|
|
|
|
// 检查缓存是否存在 |
|
|
// 检查缓存是否存在 |
|
|
if (this.elementPositionsCache[this.currentPage - 1]) { |
|
|
|
|
|
resolve(this.elementPositionsCache[this.currentPage - 1]); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// if (this.elementPositionsCache[this.currentPage - 1]) { |
|
|
|
|
|
// resolve(this.elementPositionsCache[this.currentPage - 1]); |
|
|
|
|
|
// return; |
|
|
|
|
|
// } |
|
|
|
|
|
|
|
|
const currentPageData = this.bookPages[this.currentPage - 1]; |
|
|
const currentPageData = this.bookPages[this.currentPage - 1]; |
|
|
if (!currentPageData || !Array.isArray(currentPageData)) { |
|
|
if (!currentPageData || !Array.isArray(currentPageData)) { |
|
|
@ -981,9 +975,9 @@ export default { |
|
|
if (item.type === 'text') { |
|
|
if (item.type === 'text') { |
|
|
query.select(`#text-${index}`).boundingClientRect(); |
|
|
query.select(`#text-${index}`).boundingClientRect(); |
|
|
} else if (item.type === 'image') { |
|
|
} else if (item.type === 'image') { |
|
|
query.select(`.image-container`).boundingClientRect(); |
|
|
|
|
|
|
|
|
query.select(`#image-${index}`).boundingClientRect(); |
|
|
} else if (item.type === 'video') { |
|
|
} else if (item.type === 'video') { |
|
|
query.select(`.video-content`).boundingClientRect(); |
|
|
|
|
|
|
|
|
query.select(`#video-${index}`).boundingClientRect(); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |
|
|
@ -998,19 +992,28 @@ export default { |
|
|
|
|
|
|
|
|
// 处理查询结果 |
|
|
// 处理查询结果 |
|
|
let resultIndex = 1; // 跳过第一个容器结果 |
|
|
let resultIndex = 1; // 跳过第一个容器结果 |
|
|
|
|
|
let constTop = 0 |
|
|
|
|
|
let constTopType = { |
|
|
|
|
|
'text': 10, |
|
|
|
|
|
'image': 10, |
|
|
|
|
|
'video': 10, |
|
|
|
|
|
} |
|
|
currentPageData.forEach((item, index) => { |
|
|
currentPageData.forEach((item, index) => { |
|
|
if (item && (item.type === 'text' || item.type === 'image' || item.type === 'video')) { |
|
|
if (item && (item.type === 'text' || item.type === 'image' || item.type === 'video')) { |
|
|
const elementRect = res[resultIndex]; |
|
|
const elementRect = res[resultIndex]; |
|
|
if (elementRect) { |
|
|
if (elementRect) { |
|
|
let top = elementRect.top - (containerRect.height / 2); |
|
|
|
|
|
|
|
|
const currentScrollTop = this.scrollTops[this.currentPage - 1] || 0; |
|
|
|
|
|
const top = constTop; |
|
|
|
|
|
// const top = (elementRect.top - containerRect.top) + currentScrollTop; |
|
|
elementPositions.push({ |
|
|
elementPositions.push({ |
|
|
id : elementRect.id, |
|
|
|
|
|
index: index, |
|
|
|
|
|
|
|
|
id: elementRect.id, |
|
|
|
|
|
index, |
|
|
type: item.type, |
|
|
type: item.type, |
|
|
top: top, |
|
|
|
|
|
|
|
|
top, |
|
|
height: elementRect.height, |
|
|
height: elementRect.height, |
|
|
bottom: top + elementRect.height |
|
|
bottom: top + elementRect.height |
|
|
}); |
|
|
}); |
|
|
|
|
|
constTop += elementRect.height + constTopType[item.type]; |
|
|
} |
|
|
} |
|
|
resultIndex++; |
|
|
resultIndex++; |
|
|
} |
|
|
} |
|
|
@ -1026,6 +1029,79 @@ export default { |
|
|
return elementPositions; |
|
|
return elementPositions; |
|
|
}); |
|
|
}); |
|
|
}, |
|
|
}, |
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
|
|
// 使用浏览器 DOM API 获取所有页面元素的位置信息(H5) |
|
|
|
|
|
async getAllElementPositions() { |
|
|
|
|
|
// const cached = this.elementPositionsCache[this.currentPage - 1]; |
|
|
|
|
|
// if (cached && Array.isArray(cached)) { |
|
|
|
|
|
// return cached; |
|
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
|
|
const currentPageData = this.bookPages[this.currentPage - 1]; |
|
|
|
|
|
if (!currentPageData || !Array.isArray(currentPageData)) { |
|
|
|
|
|
return []; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 确保 DOM 已更新 |
|
|
|
|
|
await this.$nextTick(); |
|
|
|
|
|
|
|
|
|
|
|
const rootEl = this.$el || document; |
|
|
|
|
|
let currentPage = this.currentPage - 1; |
|
|
|
|
|
let containerId = `scroll-container-${currentPage}`; |
|
|
|
|
|
const containerEl = (rootEl.querySelector ? rootEl.querySelector(`#${containerId}`) : null) || document.querySelector('.scroll-container'); |
|
|
|
|
|
if (!containerEl || typeof containerEl.getBoundingClientRect !== 'function') { |
|
|
|
|
|
console.warn('[getAllElementPositions] 未找到滚动容器或不支持 DOM API'); |
|
|
|
|
|
return []; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const containerRect = containerEl.getBoundingClientRect(); |
|
|
|
|
|
// console.log('document.getElementById containerEl', containerEl); |
|
|
|
|
|
|
|
|
|
|
|
const currentScrollTop = this.scrollTops[this.currentPage] || 0; |
|
|
|
|
|
const elementPositions = []; |
|
|
|
|
|
|
|
|
|
|
|
let constTop = 0 |
|
|
|
|
|
let constTopType = { |
|
|
|
|
|
'text': 10, |
|
|
|
|
|
'image': 10, |
|
|
|
|
|
'video': 10, |
|
|
|
|
|
} |
|
|
|
|
|
currentPageData.forEach((item, index) => { |
|
|
|
|
|
if (!item || !item.type) return; |
|
|
|
|
|
let el = document.querySelector(`#${containerId} #${item.type}-${index}`); |
|
|
|
|
|
|
|
|
|
|
|
// console.log('document.getElementById', `${item.type}-${index}`, el.clientHeight, el); |
|
|
|
|
|
|
|
|
|
|
|
if (el && typeof el.getBoundingClientRect === 'function') { |
|
|
|
|
|
const rect = el.getBoundingClientRect(); |
|
|
|
|
|
const top = constTop; |
|
|
|
|
|
elementPositions.push({ |
|
|
|
|
|
id: el.id || '', |
|
|
|
|
|
index, |
|
|
|
|
|
type: item.type, |
|
|
|
|
|
top, |
|
|
|
|
|
height: rect.height, |
|
|
|
|
|
bottom: top + rect.height |
|
|
|
|
|
}); |
|
|
|
|
|
constTop += rect.height + constTopType[item.type]; |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
if (elementPositions.length > 0) { |
|
|
|
|
|
this.elementPositionsCache[this.currentPage - 1] = elementPositions; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return elementPositions; |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
awaitSleep(time) { |
|
|
|
|
|
return new Promise((resolve) => { |
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
resolve(); |
|
|
|
|
|
}, time); |
|
|
|
|
|
}); |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
// 检查元素是否在可视范围内 |
|
|
// 检查元素是否在可视范围内 |
|
|
isElementInViewport(elementPosition, currentScrollTop) { |
|
|
isElementInViewport(elementPosition, currentScrollTop) { |
|
|
@ -1926,7 +2002,6 @@ export default { |
|
|
// 清理單詞語音緩存 |
|
|
// 清理單詞語音緩存 |
|
|
clearWordAudioCache() { |
|
|
clearWordAudioCache() { |
|
|
this.wordAudioCache = {}; |
|
|
this.wordAudioCache = {}; |
|
|
|
|
|
|
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
// 限制缓存大小,保留最近访问的页面 |
|
|
// 限制缓存大小,保留最近访问的页面 |
|
|
@ -2365,7 +2440,7 @@ export default { |
|
|
.card-image { |
|
|
.card-image { |
|
|
// width: 590rpx; |
|
|
// width: 590rpx; |
|
|
width: 100%; |
|
|
width: 100%; |
|
|
height: 268rpx; |
|
|
|
|
|
|
|
|
//height: 268rpx; |
|
|
border-radius: 24rpx; |
|
|
border-radius: 24rpx; |
|
|
margin: 30rpx auto; |
|
|
margin: 30rpx auto; |
|
|
// margin-bottom: 20rpx; |
|
|
// margin-bottom: 20rpx; |
|
|
|