Commit 2b2ffbc3 authored by liujiangnan's avatar liujiangnan

feat: 逐词高亮逻辑

parent c0996581
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -70,6 +70,22 @@ body {
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 {
position: fixed;
left: 50%;
......
......@@ -23,6 +23,16 @@
<template v-if="currentRects && isRectVisible">
<div v-for="(rect, idx) in currentRects" :key="idx" class="rect-layer rect-visible" :style="getRectStyle(rect)"></div>
</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>
</transition>
</div>
......@@ -45,6 +55,8 @@
<div>单词数: {{ debugInfo.totalWords }}</div>
<div>高亮索引: {{ debugInfo.highlightedIndexes.join(', ') }}</div>
<div>当前单词: {{ debugInfo.currentWord }}</div>
<div>单词矩形数: {{ debugInfo.wordRectsCount }}</div>
<div>组单词数: {{ debugInfo.groupWordsCount }}</div>
</div>
</div>
<div class="fixed-btn-group">
......
......@@ -41,6 +41,9 @@ function initializeApp(data) {
// 逐字高亮相关
currentWords: [],
highlightedWordIndexes: [],
// 单词级别矩形高亮相关
currentGroupWords: [], // 当前活跃组的单词矩形信息
highlightedWordRects: [], // 当前高亮的单词矩形
},
computed: {
currentGroups() {
......@@ -85,7 +88,9 @@ function initializeApp(data) {
currentTime: this.audio ? this.audio.currentTime : 0,
totalWords: this.currentWords.length,
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) {
if (currentPage && currentPage.groups) {
// 重置活跃组索引数组
this.activeGroupIndexes = [];
this.currentGroupWords = [];
this.highlightedWordRects = [];
// 遍历所有组,找出所有时间匹配的组
currentPage.groups.forEach((group, idx) => {
......@@ -178,6 +185,55 @@ function initializeApp(data) {
currentTime >= group.start && currentTime <= group.end) {
// 将匹配的组索引添加到活跃组数组中
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) {
this.currentWords = [];
this.highlightedWordIndexes = [];
this.currentAudioPointIndex = -1;
// 重置单词矩形高亮状态
this.currentGroupWords = [];
this.highlightedWordRects = [];
},
togglePlay() {
if (this.isPlaying) {
......@@ -317,6 +376,30 @@ function initializeApp(data) {
height: (height * imgRect.height) + 'px',
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