feat(lyric): 重构歌词渲染区域为 scroll/single/double 三路分支

This commit is contained in:
alger
2026-03-15 15:07:57 +08:00
parent c8ba6cbd44
commit 87a4773ece
+87 -25
View File
@@ -99,14 +99,15 @@
<!-- 歌词显示区域 --> <!-- 歌词显示区域 -->
<div ref="containerRef" class="lyric-container"> <div ref="containerRef" class="lyric-container">
<div class="lyric-scroll"> <!-- 滚动模式默认 -->
<div v-if="displayMode === 'scroll'" class="lyric-scroll">
<div class="lyric-wrapper" :style="wrapperStyle"> <div class="lyric-wrapper" :style="wrapperStyle">
<template v-if="staticData.lrcArray?.length > 0"> <template v-if="staticData.lrcArray?.length > 0">
<div <div
v-for="(line, index) in staticData.lrcArray" v-for="(line, index) in staticData.lrcArray"
:key="index" :key="index"
class="lyric-line" class="lyric-line"
:style="getDynamicLineStyle(line)" :style="getDynamicLineStyle(line, showTranslation)"
:class="{ :class="{
'lyric-line-current': index === currentIndex, 'lyric-line-current': index === currentIndex,
'lyric-line-passed': index < currentIndex, 'lyric-line-passed': index < currentIndex,
@@ -114,7 +115,6 @@
}" }"
> >
<div class="lyric-text" :style="{ fontSize: `${fontSize}px` }"> <div class="lyric-text" :style="{ fontSize: `${fontSize}px` }">
<!-- 逐字歌词显示 -->
<div <div
v-if="line.hasWordByWord && line.words && line.words.length > 0" v-if="line.hasWordByWord && line.words && line.words.length > 0"
class="word-by-word-lyric" class="word-by-word-lyric"
@@ -122,16 +122,16 @@
<template v-for="(word, wordIndex) in line.words" :key="wordIndex"> <template v-for="(word, wordIndex) in line.words" :key="wordIndex">
<span class="lyric-word" :style="getWordStyle(index, wordIndex, word)"> <span class="lyric-word" :style="getWordStyle(index, wordIndex, word)">
{{ word.text }} </span {{ word.text }} </span
><span class="lyric-word" v-if="word.space">&nbsp;</span></template ><span v-if="word.space" class="lyric-word">&nbsp;</span>
> </template>
</div> </div>
<!-- 普通歌词显示 -->
<span v-else class="lyric-text-inner" :style="getLyricStyle(index)"> <span v-else class="lyric-text-inner" :style="getLyricStyle(index)">
{{ line.text || '' }} {{ line.text || '' }}
</span> </span>
</div> </div>
<!-- 翻译行加入 showTranslation 控制 -->
<div <div
v-if="line.trText" v-if="showTranslation && line.trText"
class="lyric-translation" class="lyric-translation"
:style="{ fontSize: `${fontSize * 0.6}px` }" :style="{ fontSize: `${fontSize * 0.6}px` }"
> >
@@ -142,6 +142,81 @@
<div v-else class="lyric-empty">无歌词</div> <div v-else class="lyric-empty">无歌词</div>
</div> </div>
</div> </div>
<!-- 单行模式 -->
<div v-else-if="displayMode === 'single'" class="lyric-single-mode">
<template v-if="staticData.lrcArray?.length > 0">
<div class="lyric-line lyric-line-current">
<div class="lyric-text" :style="{ fontSize: `${fontSize}px` }">
<div
v-if="
staticData.lrcArray[currentIndex] != null &&
staticData.lrcArray[currentIndex].hasWordByWord &&
(staticData.lrcArray[currentIndex].words?.length ?? 0) > 0
"
class="word-by-word-lyric"
>
<template
v-for="(word, wordIndex) in staticData.lrcArray[currentIndex]!.words"
:key="wordIndex"
>
<span class="lyric-word" :style="getWordStyle(currentIndex, wordIndex, word)">
{{ word.text }} </span
><span v-if="word.space" class="lyric-word">&nbsp;</span>
</template>
</div>
<span v-else class="lyric-text-inner" :style="getLyricStyle(currentIndex)">
{{ staticData.lrcArray[currentIndex]?.text || '' }}
</span>
</div>
<div
v-if="showTranslation && staticData.lrcArray[currentIndex]?.trText"
class="lyric-translation"
:style="{ fontSize: `${fontSize * 0.6}px` }"
>
{{ staticData.lrcArray[currentIndex]?.trText }}
</div>
</div>
</template>
<div v-else class="lyric-empty">无歌词</div>
</div>
<!-- ③ 双行模式(固定分组,每 2 行为一组) -->
<div v-else class="lyric-double-mode" :class="{ 'group-fade': isGroupTransitioning }">
<template v-if="staticData.lrcArray?.length > 0">
<!-- currentGroupLines 最多 2 条,最后一组只有 1 行时自动只显示 1 行 -->
<div
v-for="line in currentGroupLines"
:key="line.index"
class="lyric-line"
:class="{ 'lyric-line-current': line.index === currentIndex }"
>
<div class="lyric-text" :style="{ fontSize: `${fontSize}px` }">
<div
v-if="line.hasWordByWord && line.words && line.words.length > 0"
class="word-by-word-lyric"
>
<template v-for="(word, wordIndex) in line.words" :key="wordIndex">
<span class="lyric-word" :style="getWordStyle(line.index, wordIndex, word)">
{{ word.text }} </span
><span v-if="word.space" class="lyric-word">&nbsp;</span>
</template>
</div>
<span v-else class="lyric-text-inner" :style="getLyricStyle(line.index)">
{{ line.text || '' }}
</span>
</div>
<div
v-if="showTranslation && line.trText"
class="lyric-translation"
:style="{ fontSize: `${fontSize * 0.6}px` }"
>
{{ line.trText }}
</div>
</div>
</template>
<div v-else class="lyric-empty">无歌词</div>
</div>
</div> </div>
</div> </div>
</template> </template>
@@ -407,22 +482,13 @@ const wrapperStyle = computed(() => {
}); });
// 新增:根据是否有翻译文本动态计算每行的样式 // 新增:根据是否有翻译文本动态计算每行的样式
const getDynamicLineStyle = (line: { text: string; trText: string }) => { const getDynamicLineStyle = (line: { text: string; trText: string }, withTranslation = true) => {
// 默认行高
const defaultHeight = lineHeight.value; const defaultHeight = lineHeight.value;
if (withTranslation && line.trText) {
// 如果有翻译文本,增加额外高度
if (line.trText) {
// 计算翻译文本的额外高度 (字体大小的0.6倍 * 行高比例1.4)
const extraHeight = Math.round(fontSize.value * 0.6 * 1.4); const extraHeight = Math.round(fontSize.value * 0.6 * 1.4);
return { return { height: `${defaultHeight + extraHeight}px` };
height: `${defaultHeight + extraHeight}px`
};
} }
return { height: `${defaultHeight}px` };
return {
height: `${defaultHeight}px`
};
}; };
// 更新容器高度和行高 // 更新容器高度和行高
@@ -814,11 +880,7 @@ const validateAndFixColorSettings = () => {
// 暴露函数 // 暴露函数
defineExpose({ defineExpose({
resetThemeColor, resetThemeColor,
validateAndFixColorSettings, validateAndFixColorSettings
// 以下供后续 Task(模板更新)使用,避免 noUnusedLocals 报错
hasTranslation,
currentGroupLines,
isGroupTransitioning
}); });
const updateCSSVariable = (name: string, value: string) => { const updateCSSVariable = (name: string, value: string) => {