feat: 移动端歌词点击跳转 优化国际化和移动端逐字歌词

This commit is contained in:
alger
2025-10-12 17:38:45 +08:00
parent a5d3ff359c
commit 316d5932e3
8 changed files with 66 additions and 93 deletions

View File

@@ -29,7 +29,8 @@ export default {
list: 'Next'
},
lrc: {
noLrc: 'No lyrics, please enjoy'
noLrc: 'No lyrics, please enjoy',
noAutoScroll: 'This lyrics does not support auto-scroll'
},
reparse: {
title: 'Select Music Source',

View File

@@ -1,38 +0,0 @@
# 日本語翻訳 (Japanese Translation)
このディレクトリには、AlgerMusicPlayerの日本語翻訳ファイルが含まれています。
## ファイル構成
- `artist.ts` - アーティスト関連の翻訳
- `common.ts` - 共通の翻訳(ボタン、メッセージなど)
- `comp.ts` - コンポーネント関連の翻訳
- `donation.ts` - 寄付関連の翻訳
- `download.ts` - ダウンロード管理の翻訳
- `favorite.ts` - お気に入り機能の翻訳
- `history.ts` - 履歴機能の翻訳
- `login.ts` - ログイン関連の翻訳
- `player.ts` - プレイヤー機能の翻訳
- `search.ts` - 検索機能の翻訳
- `settings.ts` - 設定画面の翻訳
- `songItem.ts` - 楽曲アイテムの翻訳
- `user.ts` - ユーザー関連の翻訳
- `index.ts` - すべての翻訳をエクスポートするメインファイル
## 使用方法
アプリケーション内で言語を日本語に切り替えるには:
1. 設定画面を開く
2. 「言語設定」セクションを見つける
3. ドロップダウンメニューから「日本語」を選択
## 翻訳の改善
翻訳の改善や修正がある場合は、該当するファイルを編集してプルリクエストを送信してください。
## 注意事項
- すべての翻訳キーは中国語版と英語版に対応しています
- 新しい機能が追加された場合は、対応する日本語翻訳も追加する必要があります
- 文字化けを避けるため、ファイルはUTF-8エンコーディングで保存してください

View File

@@ -29,7 +29,8 @@ export default {
list: '自動で次の曲を再生'
},
lrc: {
noLrc: '歌詞がありません。お楽しみください'
noLrc: '歌詞がありません。お楽しみください',
noAutoScroll: '本歌詞は自動スクロールをサポートしていません'
},
reparse: {
title: '解析音源を選択',

View File

@@ -29,7 +29,8 @@ export default {
list: '자동으로 다음 곡 재생'
},
lrc: {
noLrc: '가사가 없습니다. 음악을 감상해주세요'
noLrc: '가사가 없습니다. 음악을 감상해주세요',
noAutoScroll: '본 가사는 자동 스크롤을 지원하지 않습니다'
},
reparse: {
title: '음원 선택',

View File

@@ -29,7 +29,8 @@ export default {
list: '自动播放下一个'
},
lrc: {
noLrc: '暂无歌词, 请欣赏'
noLrc: '暂无歌词, 请欣赏',
noAutoScroll: '本歌词不支持自动滚动'
},
reparse: {
title: '选择解析音源',

View File

@@ -29,7 +29,8 @@ export default {
list: '自動播放下一個'
},
lrc: {
noLrc: '暫無歌詞, 請欣賞'
noLrc: '暫無歌詞, 請欣賞',
noAutoScroll: '本歌詞不支持自動滾動'
},
reparse: {
title: '選擇解析音源',

View File

@@ -113,7 +113,7 @@
</div>
<!-- 无时间戳歌词提示 -->
<div v-if="!supportAutoScroll" class="music-lrc-text no-scroll-tip">
<span>本歌词不支持自动滚动</span>
<span>{{ t('player.lrc.noAutoScroll') }}</span>
</div>
<div
v-for="(item, index) in lrcArray"

View File

@@ -51,7 +51,7 @@
<div class="lyrics-padding-top"></div>
<!-- 无时间戳歌词提示 -->
<div v-if="!supportAutoScroll" class="lyric-line no-scroll-tip">
<span>本歌词不支持自动滚动</span>
<span>{{ t('player.lrc.noAutoScroll') }}</span>
</div>
<div
v-for="(item, index) in lrcArray"
@@ -62,7 +62,7 @@
'now-text': index === nowIndex,
'hover-text': item.text && item.startTime !== -1
}"
@click="item.startTime !== -1 ? jumpToLyricTime(index) : null"
@click="item.startTime !== -1 ? setAudioTime(index) : null"
>
<!-- 逐字歌词显示 -->
<div
@@ -143,14 +143,14 @@
v-if="line.hasWordByWord && line.words && line.words.length > 0"
class="word-by-word-lyric"
>
<span
v-for="(word, wordIndex) in line.words"
:key="wordIndex"
class="lyric-word"
:style="getWordStyle(line.originalIndex, wordIndex, word)"
<template v-for="(word, wordIndex) in line.words" :key="wordIndex">
<span
class="lyric-word"
:style="getWordStyle(line.originalIndex, wordIndex, word)"
>
{{ word.text }}</span
><span v-if="word.space">&nbsp;</span></template
>
{{ word.text }}
</span>
</div>
<!-- 普通歌词显示 -->
<span v-else>{{ line.text }}</span>
@@ -253,7 +253,7 @@
<div class="lyrics-padding-top"></div>
<!-- 无时间戳歌词提示 -->
<div v-if="!supportAutoScroll" class="lyric-line no-scroll-tip">
<span>本歌词不支持自动滚动</span>
<span>{{ t('player.lrc.noAutoScroll') }}</span>
</div>
<div
v-for="(item, index) in lrcArray"
@@ -264,7 +264,7 @@
'now-text': index === nowIndex,
'hover-text': item.text && item.startTime !== -1
}"
@click="item.startTime !== -1 ? jumpToLyricTime(index) : null"
@click="item.startTime !== -1 ? setAudioTime(index) : null"
>
<!-- 逐字歌词显示 -->
<div
@@ -371,10 +371,12 @@ import { useI18n } from 'vue-i18n';
import {
allTime,
artistList,
correctionTime,
lrcArray,
nowIndex,
nowTime,
playMusic,
setAudioTime,
sound,
textColors,
useLyricProgress
@@ -431,6 +433,7 @@ const isTouchScrolling = ref(false);
const touchStartY = ref(0);
const lastScrollTop = ref(0);
const autoScrollTimer = ref<number | null>(null);
const isSongChanging = ref(false);
// 横屏检测相关
const { width, height } = useWindowSize();
@@ -530,6 +533,9 @@ const scrollToCurrentLyric = (immediate = false, customScrollerRef?: HTMLElement
watch(nowIndex, (newIndex, oldIndex) => {
console.log(`歌词索引变化: ${oldIndex} -> ${newIndex}`);
// 歌曲切换时不自动滚动
if (isSongChanging.value) return;
// 在竖屏全屏歌词模式下滚动
if (showFullLyrics.value) {
nextTick(() => {
@@ -980,6 +986,38 @@ watch(
{ immediate: true }
);
// 添加对 playMusic.id 的监听,歌曲切换时滚动到顶部
watch(
() => playMusic.value.id,
(newId, oldId) => {
// 只在歌曲真正切换时滚动到顶部
if (newId !== oldId && newId) {
isSongChanging.value = true;
// 延迟滚动,确保 nowIndex 已重置
setTimeout(() => {
// 在全屏歌词模式下滚动到顶部
if (showFullLyrics.value && lyricsScrollerRef.value) {
lyricsScrollerRef.value.scrollTo({
top: 0,
behavior: 'smooth'
});
}
// 在横屏模式下滚动到顶部
else if (isLandscape.value && landscapeLyricsRef.value) {
landscapeLyricsRef.value.scrollTo({
top: 0,
behavior: 'smooth'
});
}
// 延迟恢复自动滚动,等待歌词数据更新
setTimeout(() => {
isSongChanging.value = false;
}, 300);
}, 100);
}
}
);
// 加载保存的配置
onMounted(() => {
const savedConfig = localStorage.getItem('music-full-config');
@@ -1024,42 +1062,6 @@ watch(isVisible, (newVal) => {
}
});
// 通过点击跳转到歌词对应时间点
const jumpToLyricTime = (index: number) => {
if (lrcArray.value[index] && 'time' in lrcArray.value[index] && sound.value) {
// 使用类型断言确保time属性存在
const lrcItem = lrcArray.value[index] as { time: number; text: string; trText?: string };
const time = lrcItem.time / 1000;
// 更新播放位置
sound.value.seek(time);
nowTime.value = time;
// 显示反馈动画 - 处理两种模式下的歌词行
const normalEl = document.getElementById(`lyric-line-${index}`);
const landscapeEl = document.getElementById(`landscape-lyric-line-${index}`);
// 根据当前模式获取正确的元素并添加动画效果
const activeEl = isLandscape.value ? landscapeEl : normalEl;
if (activeEl) {
activeEl.classList.add('clicked');
setTimeout(() => {
activeEl.classList.remove('clicked');
}, 300);
}
// 如果歌词索引没有变化(例如点击当前行),手动触发滚动
if (nowIndex.value === index) {
if (isLandscape.value && !showFullLyrics.value) {
scrollToCurrentLyric(true, landscapeLyricsRef.value);
} else if (showFullLyrics.value) {
scrollToCurrentLyric(true);
}
}
}
};
// 添加getLrcStyle函数
const { getLrcStyle: originalLrcStyle } = useLyricProgress();
@@ -1101,8 +1103,8 @@ const getWordStyle = (lineIndex: number, _wordIndex: number, word: any) => {
};
}
// 当前行的逐字效果
const currentTime = nowTime.value * 1000; // 转换为毫秒确保与word时间单位一致
// 当前行的逐字效果,应用歌词矫正时间
const currentTime = (nowTime.value + correctionTime.value) * 1000; // 转换为毫秒确保与word时间单位一致
// 直接使用绝对时间比较
const wordStartTime = word.startTime; // 单词开始的绝对时间(毫秒)
@@ -1874,6 +1876,10 @@ const getWordStyle = (lineIndex: number, _wordIndex: number, word: any) => {
}
}
}
.word-by-word-lyric {
@apply justify-start;
}
}
.unified-controls {