Commit 2b2ffbc3 authored by liujiangnan's avatar liujiangnan

feat: 逐词高亮逻辑

parent c0996581
This diff is collapsed.
...@@ -70,6 +70,22 @@ body { ...@@ -70,6 +70,22 @@ body {
transform: scale(1); transform: scale(1);
} }
/* 单词级别矩形高亮样式 */
.word-rect-layer {
position: absolute;
background-color: rgba(255, 235, 59, 0.3);
border: 2px solid #ffeb3b;
border-radius: 4px;
pointer-events: none;
z-index: 3;
box-shadow: 0 0 8px rgba(255, 235, 59, 0.5);
}
.word-rect-layer:hover {
background-color: rgba(255, 235, 59, 0.5);
box-shadow: 0 0 12px rgba(255, 235, 59, 0.8);
}
.subtitle-bar { .subtitle-bar {
position: fixed; position: fixed;
left: 50%; left: 50%;
......
...@@ -23,6 +23,16 @@ ...@@ -23,6 +23,16 @@
<template v-if="currentRects && isRectVisible"> <template v-if="currentRects && isRectVisible">
<div v-for="(rect, idx) in currentRects" :key="idx" class="rect-layer rect-visible" :style="getRectStyle(rect)"></div> <div v-for="(rect, idx) in currentRects" :key="idx" class="rect-layer rect-visible" :style="getRectStyle(rect)"></div>
</template> </template>
<!-- 单词级别矩形高亮 -->
<template v-if="highlightedWordRects && isRectVisible">
<div
v-for="(wordRect, idx) in highlightedWordRects"
:key="'word-' + idx"
class="word-rect-layer"
:style="getWordRectStyle(wordRect)"
:title="wordRect.text"
></div>
</template>
</div> </div>
</transition> </transition>
</div> </div>
...@@ -45,6 +55,8 @@ ...@@ -45,6 +55,8 @@
<div>单词数: {{ debugInfo.totalWords }}</div> <div>单词数: {{ debugInfo.totalWords }}</div>
<div>高亮索引: {{ debugInfo.highlightedIndexes.join(', ') }}</div> <div>高亮索引: {{ debugInfo.highlightedIndexes.join(', ') }}</div>
<div>当前单词: {{ debugInfo.currentWord }}</div> <div>当前单词: {{ debugInfo.currentWord }}</div>
<div>单词矩形数: {{ debugInfo.wordRectsCount }}</div>
<div>组单词数: {{ debugInfo.groupWordsCount }}</div>
</div> </div>
</div> </div>
<div class="fixed-btn-group"> <div class="fixed-btn-group">
......
...@@ -41,6 +41,9 @@ function initializeApp(data) { ...@@ -41,6 +41,9 @@ function initializeApp(data) {
// 逐字高亮相关 // 逐字高亮相关
currentWords: [], currentWords: [],
highlightedWordIndexes: [], highlightedWordIndexes: [],
// 单词级别矩形高亮相关
currentGroupWords: [], // 当前活跃组的单词矩形信息
highlightedWordRects: [], // 当前高亮的单词矩形
}, },
computed: { computed: {
currentGroups() { currentGroups() {
...@@ -85,7 +88,9 @@ function initializeApp(data) { ...@@ -85,7 +88,9 @@ function initializeApp(data) {
currentTime: this.audio ? this.audio.currentTime : 0, currentTime: this.audio ? this.audio.currentTime : 0,
totalWords: this.currentWords.length, totalWords: this.currentWords.length,
highlightedIndexes: this.highlightedWordIndexes, highlightedIndexes: this.highlightedWordIndexes,
currentWord: currentWord ? `${currentWord.word} (${currentWord.start}-${currentWord.end})` : '无' currentWord: currentWord ? `${currentWord.word} (${currentWord.start}-${currentWord.end})` : '无',
wordRectsCount: this.highlightedWordRects.length,
groupWordsCount: this.currentGroupWords.length
}; };
} }
}, },
...@@ -171,6 +176,8 @@ function initializeApp(data) { ...@@ -171,6 +176,8 @@ function initializeApp(data) {
if (currentPage && currentPage.groups) { if (currentPage && currentPage.groups) {
// 重置活跃组索引数组 // 重置活跃组索引数组
this.activeGroupIndexes = []; this.activeGroupIndexes = [];
this.currentGroupWords = [];
this.highlightedWordRects = [];
// 遍历所有组,找出所有时间匹配的组 // 遍历所有组,找出所有时间匹配的组
currentPage.groups.forEach((group, idx) => { currentPage.groups.forEach((group, idx) => {
...@@ -178,6 +185,55 @@ function initializeApp(data) { ...@@ -178,6 +185,55 @@ function initializeApp(data) {
currentTime >= group.start && currentTime <= group.end) { currentTime >= group.start && currentTime <= group.end) {
// 将匹配的组索引添加到活跃组数组中 // 将匹配的组索引添加到活跃组数组中
this.activeGroupIndexes.push(idx); this.activeGroupIndexes.push(idx);
// 处理单词级别的矩形高亮
if (group.group && Array.isArray(group.group)) {
group.group.forEach(item => {
if (item.words && Array.isArray(item.words)) {
// 将单词信息添加到当前组单词列表
item.words.forEach(word => {
this.currentGroupWords.push({
...word,
groupIndex: idx,
itemIndex: group.group.indexOf(item)
});
});
// 只高亮当前正在播放的单词
if (this.currentWords.length > 0 && this.highlightedWordIndexes.length > 0) {
this.highlightedWordIndexes.forEach(hIdx => {
const audioWord = this.currentWords[hIdx];
// 收集所有匹配的页面单词
const matchingWords = [];
item.words.forEach((word, wordIdx) => {
if (
audioWord &&
word.text &&
audioWord.word &&
word.text.trim().toLowerCase() === audioWord.word.trim().toLowerCase()
) {
matchingWords.push({
...word,
groupIndex: idx,
itemIndex: group.group.indexOf(item),
audioWord,
wordIdx
});
}
});
// 根据音频单词的索引选择对应的页面单词
if (matchingWords.length > 0) {
// 如果匹配的单词数量大于音频索引,选择对应位置的单词
// 否则选择最后一个匹配的单词
const selectedWord = matchingWords[hIdx] || matchingWords[matchingWords.length - 1];
this.highlightedWordRects.push(selectedWord);
}
});
}
}
});
}
} }
}); });
...@@ -246,6 +302,9 @@ function initializeApp(data) { ...@@ -246,6 +302,9 @@ function initializeApp(data) {
this.currentWords = []; this.currentWords = [];
this.highlightedWordIndexes = []; this.highlightedWordIndexes = [];
this.currentAudioPointIndex = -1; this.currentAudioPointIndex = -1;
// 重置单词矩形高亮状态
this.currentGroupWords = [];
this.highlightedWordRects = [];
}, },
togglePlay() { togglePlay() {
if (this.isPlaying) { if (this.isPlaying) {
...@@ -317,6 +376,30 @@ function initializeApp(data) { ...@@ -317,6 +376,30 @@ function initializeApp(data) {
height: (height * imgRect.height) + 'px', height: (height * imgRect.height) + 'px',
display: rect.visible === false ? 'none' : 'block' display: rect.visible === false ? 'none' : 'block'
}; };
},
getWordRectStyle(wordRect) {
if (!wordRect || !wordRect.rect || !this.imageSize.width || !this.imageSize.height) return {};
// 获取图片元素
const img = this.$refs.mainImg;
if (!img) return {};
// 获取图片的实际显示尺寸和位置
const imgRect = img.getBoundingClientRect();
const x = parseFloat(wordRect.rect.x);
const y = parseFloat(wordRect.rect.y);
const width = parseFloat(wordRect.rect.width);
const height = parseFloat(wordRect.rect.height);
return {
position: 'absolute',
left: (x * imgRect.width + imgRect.left) + 'px',
top: (y * imgRect.height + imgRect.top) + 'px',
width: (width * imgRect.width) + 'px',
height: (height * imgRect.height) + 'px',
backgroundColor: 'rgba(255, 235, 59, 0.3)',
border: '2px solid #ffeb3b',
borderRadius: '4px',
pointerEvents: 'none',
zIndex: 3
};
} }
} }
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment