mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-05-16 17:47:28 +08:00
feat: 移动端歌词点击跳转 优化国际化和移动端逐字歌词
This commit is contained in:
@@ -29,7 +29,8 @@ export default {
|
|||||||
list: 'Next'
|
list: 'Next'
|
||||||
},
|
},
|
||||||
lrc: {
|
lrc: {
|
||||||
noLrc: 'No lyrics, please enjoy'
|
noLrc: 'No lyrics, please enjoy',
|
||||||
|
noAutoScroll: 'This lyrics does not support auto-scroll'
|
||||||
},
|
},
|
||||||
reparse: {
|
reparse: {
|
||||||
title: 'Select Music Source',
|
title: 'Select Music Source',
|
||||||
|
|||||||
@@ -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エンコーディングで保存してください
|
|
||||||
@@ -29,7 +29,8 @@ export default {
|
|||||||
list: '自動で次の曲を再生'
|
list: '自動で次の曲を再生'
|
||||||
},
|
},
|
||||||
lrc: {
|
lrc: {
|
||||||
noLrc: '歌詞がありません。お楽しみください'
|
noLrc: '歌詞がありません。お楽しみください',
|
||||||
|
noAutoScroll: '本歌詞は自動スクロールをサポートしていません'
|
||||||
},
|
},
|
||||||
reparse: {
|
reparse: {
|
||||||
title: '解析音源を選択',
|
title: '解析音源を選択',
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ export default {
|
|||||||
list: '자동으로 다음 곡 재생'
|
list: '자동으로 다음 곡 재생'
|
||||||
},
|
},
|
||||||
lrc: {
|
lrc: {
|
||||||
noLrc: '가사가 없습니다. 음악을 감상해주세요'
|
noLrc: '가사가 없습니다. 음악을 감상해주세요',
|
||||||
|
noAutoScroll: '본 가사는 자동 스크롤을 지원하지 않습니다'
|
||||||
},
|
},
|
||||||
reparse: {
|
reparse: {
|
||||||
title: '음원 선택',
|
title: '음원 선택',
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ export default {
|
|||||||
list: '自动播放下一个'
|
list: '自动播放下一个'
|
||||||
},
|
},
|
||||||
lrc: {
|
lrc: {
|
||||||
noLrc: '暂无歌词, 请欣赏'
|
noLrc: '暂无歌词, 请欣赏',
|
||||||
|
noAutoScroll: '本歌词不支持自动滚动'
|
||||||
},
|
},
|
||||||
reparse: {
|
reparse: {
|
||||||
title: '选择解析音源',
|
title: '选择解析音源',
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ export default {
|
|||||||
list: '自動播放下一個'
|
list: '自動播放下一個'
|
||||||
},
|
},
|
||||||
lrc: {
|
lrc: {
|
||||||
noLrc: '暫無歌詞, 請欣賞'
|
noLrc: '暫無歌詞, 請欣賞',
|
||||||
|
noAutoScroll: '本歌詞不支持自動滾動'
|
||||||
},
|
},
|
||||||
reparse: {
|
reparse: {
|
||||||
title: '選擇解析音源',
|
title: '選擇解析音源',
|
||||||
|
|||||||
@@ -113,7 +113,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- 无时间戳歌词提示 -->
|
<!-- 无时间戳歌词提示 -->
|
||||||
<div v-if="!supportAutoScroll" class="music-lrc-text no-scroll-tip">
|
<div v-if="!supportAutoScroll" class="music-lrc-text no-scroll-tip">
|
||||||
<span>本歌词不支持自动滚动</span>
|
<span>{{ t('player.lrc.noAutoScroll') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-for="(item, index) in lrcArray"
|
v-for="(item, index) in lrcArray"
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
<div class="lyrics-padding-top"></div>
|
<div class="lyrics-padding-top"></div>
|
||||||
<!-- 无时间戳歌词提示 -->
|
<!-- 无时间戳歌词提示 -->
|
||||||
<div v-if="!supportAutoScroll" class="lyric-line no-scroll-tip">
|
<div v-if="!supportAutoScroll" class="lyric-line no-scroll-tip">
|
||||||
<span>本歌词不支持自动滚动</span>
|
<span>{{ t('player.lrc.noAutoScroll') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-for="(item, index) in lrcArray"
|
v-for="(item, index) in lrcArray"
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
'now-text': index === nowIndex,
|
'now-text': index === nowIndex,
|
||||||
'hover-text': item.text && item.startTime !== -1
|
'hover-text': item.text && item.startTime !== -1
|
||||||
}"
|
}"
|
||||||
@click="item.startTime !== -1 ? jumpToLyricTime(index) : null"
|
@click="item.startTime !== -1 ? setAudioTime(index) : null"
|
||||||
>
|
>
|
||||||
<!-- 逐字歌词显示 -->
|
<!-- 逐字歌词显示 -->
|
||||||
<div
|
<div
|
||||||
@@ -143,14 +143,14 @@
|
|||||||
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"
|
||||||
>
|
>
|
||||||
<span
|
<template v-for="(word, wordIndex) in line.words" :key="wordIndex">
|
||||||
v-for="(word, wordIndex) in line.words"
|
<span
|
||||||
:key="wordIndex"
|
class="lyric-word"
|
||||||
class="lyric-word"
|
:style="getWordStyle(line.originalIndex, wordIndex, word)"
|
||||||
:style="getWordStyle(line.originalIndex, wordIndex, word)"
|
>
|
||||||
|
{{ word.text }}</span
|
||||||
|
><span v-if="word.space"> </span></template
|
||||||
>
|
>
|
||||||
{{ word.text }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- 普通歌词显示 -->
|
<!-- 普通歌词显示 -->
|
||||||
<span v-else>{{ line.text }}</span>
|
<span v-else>{{ line.text }}</span>
|
||||||
@@ -253,7 +253,7 @@
|
|||||||
<div class="lyrics-padding-top"></div>
|
<div class="lyrics-padding-top"></div>
|
||||||
<!-- 无时间戳歌词提示 -->
|
<!-- 无时间戳歌词提示 -->
|
||||||
<div v-if="!supportAutoScroll" class="lyric-line no-scroll-tip">
|
<div v-if="!supportAutoScroll" class="lyric-line no-scroll-tip">
|
||||||
<span>本歌词不支持自动滚动</span>
|
<span>{{ t('player.lrc.noAutoScroll') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-for="(item, index) in lrcArray"
|
v-for="(item, index) in lrcArray"
|
||||||
@@ -264,7 +264,7 @@
|
|||||||
'now-text': index === nowIndex,
|
'now-text': index === nowIndex,
|
||||||
'hover-text': item.text && item.startTime !== -1
|
'hover-text': item.text && item.startTime !== -1
|
||||||
}"
|
}"
|
||||||
@click="item.startTime !== -1 ? jumpToLyricTime(index) : null"
|
@click="item.startTime !== -1 ? setAudioTime(index) : null"
|
||||||
>
|
>
|
||||||
<!-- 逐字歌词显示 -->
|
<!-- 逐字歌词显示 -->
|
||||||
<div
|
<div
|
||||||
@@ -371,10 +371,12 @@ import { useI18n } from 'vue-i18n';
|
|||||||
import {
|
import {
|
||||||
allTime,
|
allTime,
|
||||||
artistList,
|
artistList,
|
||||||
|
correctionTime,
|
||||||
lrcArray,
|
lrcArray,
|
||||||
nowIndex,
|
nowIndex,
|
||||||
nowTime,
|
nowTime,
|
||||||
playMusic,
|
playMusic,
|
||||||
|
setAudioTime,
|
||||||
sound,
|
sound,
|
||||||
textColors,
|
textColors,
|
||||||
useLyricProgress
|
useLyricProgress
|
||||||
@@ -431,6 +433,7 @@ const isTouchScrolling = ref(false);
|
|||||||
const touchStartY = ref(0);
|
const touchStartY = ref(0);
|
||||||
const lastScrollTop = ref(0);
|
const lastScrollTop = ref(0);
|
||||||
const autoScrollTimer = ref<number | null>(null);
|
const autoScrollTimer = ref<number | null>(null);
|
||||||
|
const isSongChanging = ref(false);
|
||||||
|
|
||||||
// 横屏检测相关
|
// 横屏检测相关
|
||||||
const { width, height } = useWindowSize();
|
const { width, height } = useWindowSize();
|
||||||
@@ -530,6 +533,9 @@ const scrollToCurrentLyric = (immediate = false, customScrollerRef?: HTMLElement
|
|||||||
watch(nowIndex, (newIndex, oldIndex) => {
|
watch(nowIndex, (newIndex, oldIndex) => {
|
||||||
console.log(`歌词索引变化: ${oldIndex} -> ${newIndex}`);
|
console.log(`歌词索引变化: ${oldIndex} -> ${newIndex}`);
|
||||||
|
|
||||||
|
// 歌曲切换时不自动滚动
|
||||||
|
if (isSongChanging.value) return;
|
||||||
|
|
||||||
// 在竖屏全屏歌词模式下滚动
|
// 在竖屏全屏歌词模式下滚动
|
||||||
if (showFullLyrics.value) {
|
if (showFullLyrics.value) {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
@@ -980,6 +986,38 @@ watch(
|
|||||||
{ immediate: true }
|
{ 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(() => {
|
onMounted(() => {
|
||||||
const savedConfig = localStorage.getItem('music-full-config');
|
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函数
|
// 添加getLrcStyle函数
|
||||||
const { getLrcStyle: originalLrcStyle } = useLyricProgress();
|
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; // 单词开始的绝对时间(毫秒)
|
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 {
|
.unified-controls {
|
||||||
|
|||||||
Reference in New Issue
Block a user