feat: 复制歌词

This commit is contained in:
Younglina 2024-08-27 13:21:01 +08:00
parent 481ba6bce3
commit 9d807d1d63
6 changed files with 76 additions and 2 deletions

View File

@ -244,6 +244,8 @@ export default {
minePlaylists: 'My Playlists', minePlaylists: 'My Playlists',
likedPlaylists: 'Liked Playlists', likedPlaylists: 'Liked Playlists',
cardiacMode: 'Cardiac Mode', cardiacMode: 'Cardiac Mode',
copyLyric: 'Copy Lyric',
copyLyricWithTranslation: 'Copy Lyric With Translation',
}, },
toast: { toast: {
savedToPlaylist: 'Saved to playlist', savedToPlaylist: 'Saved to playlist',

View File

@ -230,6 +230,8 @@ export default {
minePlaylists: 'My Playlists', minePlaylists: 'My Playlists',
likedPlaylists: 'Liked Playlists', likedPlaylists: 'Liked Playlists',
cardiacMode: 'Cardiac Mode', cardiacMode: 'Cardiac Mode',
copyLyric: 'Copy Lyric',
copyLyricWithTranslation: 'Copy Lyric With Translation',
}, },
toast: { toast: {
savedToMyLikedSongs: 'Beğendiğim Müziklere Kaydet', savedToMyLikedSongs: 'Beğendiğim Müziklere Kaydet',

View File

@ -243,6 +243,8 @@ export default {
minePlaylists: '创建的歌单', minePlaylists: '创建的歌单',
likedPlaylists: '收藏的歌单', likedPlaylists: '收藏的歌单',
cardiacMode: '心动模式', cardiacMode: '心动模式',
copyLyric: '复制歌词',
copyLyricWithTranslation: '复制歌词(含翻译)',
}, },
toast: { toast: {
savedToPlaylist: '已添加到歌单', savedToPlaylist: '已添加到歌单',

View File

@ -240,6 +240,8 @@ export default {
minePlaylists: '我建立的歌單', minePlaylists: '我建立的歌單',
likedPlaylists: '收藏的歌單', likedPlaylists: '收藏的歌單',
cardiacMode: '心動模式', cardiacMode: '心動模式',
copyLyric: '複製歌詞',
copyLyricWithTranslation: '複製歌詞(含翻譯)',
}, },
toast: { toast: {
savedToPlaylist: '已新增至歌單', savedToPlaylist: '已新增至歌單',

View File

@ -84,3 +84,30 @@ function trimContent(content) {
let t = content.trim(); let t = content.trim();
return t.length < 1 ? content : t; return t.length < 1 ? content : t;
} }
/**
* @param {string} lyric
*/
export async function copyLyric(lyric) {
const textToCopy = lyric;
if (navigator.clipboard && navigator.clipboard.writeText) {
try {
await navigator.clipboard.writeText(textToCopy);
} catch (err) {
alert('复制失败,请手动复制!');
}
} else {
const tempInput = document.createElement('textarea');
tempInput.value = textToCopy;
tempInput.style.position = 'absolute';
tempInput.style.left = '-9999px';
document.body.appendChild(tempInput);
tempInput.select();
try {
document.execCommand('copy');
} catch (err) {
alert('复制失败,请手动复制!');
}
document.body.removeChild(tempInput);
}
}

View File

@ -248,7 +248,11 @@
@dblclick="clickLyricLine(line.time, true)" @dblclick="clickLyricLine(line.time, true)"
> >
<div class="content"> <div class="content">
<span v-if="line.contents[0]">{{ line.contents[0] }}</span> <span
v-if="line.contents[0]"
@click.right="openLyricMenu($event, line, 0)"
>{{ line.contents[0] }}</span
>
<br /> <br />
<span <span
v-if=" v-if="
@ -256,10 +260,26 @@
$store.state.settings.showLyricsTranslation $store.state.settings.showLyricsTranslation
" "
class="translation" class="translation"
@click.right="openLyricMenu($event, line, 1)"
>{{ line.contents[1] }}</span >{{ line.contents[1] }}</span
> >
</div> </div>
</div> </div>
<ContextMenu v-if="!noLyric" ref="lyricMenu">
<div class="item" @click="copyLyric(false)">{{
$t('contextMenu.copyLyric')
}}</div>
<div
v-if="
rightClickLyric &&
rightClickLyric.contents[1] &&
$store.state.settings.showLyricsTranslation
"
class="item"
@click="copyLyric(true)"
>{{ $t('contextMenu.copyLyricWithTranslation') }}</div
>
</ContextMenu>
</div> </div>
</transition> </transition>
</div> </div>
@ -284,9 +304,10 @@
import { mapState, mapMutations, mapActions } from 'vuex'; import { mapState, mapMutations, mapActions } from 'vuex';
import VueSlider from 'vue-slider-component'; import VueSlider from 'vue-slider-component';
import ContextMenu from '@/components/ContextMenu.vue';
import { formatTrackTime } from '@/utils/common'; import { formatTrackTime } from '@/utils/common';
import { getLyric } from '@/api/track'; import { getLyric } from '@/api/track';
import { lyricParser } from '@/utils/lyrics'; import { lyricParser, copyLyric } from '@/utils/lyrics';
import ButtonIcon from '@/components/ButtonIcon.vue'; import ButtonIcon from '@/components/ButtonIcon.vue';
import * as Vibrant from 'node-vibrant/dist/vibrant.worker.min.js'; import * as Vibrant from 'node-vibrant/dist/vibrant.worker.min.js';
import Color from 'color'; import Color from 'color';
@ -299,6 +320,7 @@ export default {
components: { components: {
VueSlider, VueSlider,
ButtonIcon, ButtonIcon,
ContextMenu,
}, },
data() { data() {
return { return {
@ -312,6 +334,7 @@ export default {
background: '', background: '',
date: this.formatTime(new Date()), date: this.formatTime(new Date()),
isFullscreen: !!document.fullscreenElement, isFullscreen: !!document.fullscreenElement,
rightClickLyric: null,
}; };
}, },
computed: { computed: {
@ -587,6 +610,21 @@ export default {
this.player.play(); this.player.play();
} }
}, },
openLyricMenu(e, lyric, idx) {
this.rightClickLyric = { ...lyric, idx };
this.$refs.lyricMenu.openMenu(e);
e.preventDefault();
},
copyLyric(withTranslation) {
if (this.rightClickLyric) {
const idx = this.rightClickLyric.idx;
if (!withTranslation) {
copyLyric(this.rightClickLyric.contents[idx]);
} else {
copyLyric(this.rightClickLyric.contents.join(' '));
}
}
},
setLyricsInterval() { setLyricsInterval() {
this.lyricsInterval = setInterval(() => { this.lyricsInterval = setInterval(() => {
const progress = this.player.seek(null, false) ?? 0; const progress = this.player.seek(null, false) ?? 0;
@ -926,6 +964,7 @@ export default {
transform-origin: center left; transform-origin: center left;
transform: scale(0.95); transform: scale(0.95);
transition: all 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94); transition: all 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);
user-select: none;
span { span {
opacity: 0.28; opacity: 0.28;