mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-03 14:20:50 +08:00
✨ feat: 优化歌词组件和移动端界面设计
This commit is contained in:
@@ -14,7 +14,7 @@ const { t } = useI18n();
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="lyric-correction-btns-mac absolute right-0 bottom-4 flex flex-col items-center space-y-1 z-50 select-none transition-opacity duration-200 opacity-0 pointer-events-none"
|
||||
class="lyric-correction"
|
||||
>
|
||||
<n-tooltip placement="right">
|
||||
<template #trigger>
|
||||
@@ -46,8 +46,18 @@ const { t } = useI18n();
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="scss">
|
||||
.lyric-correction {
|
||||
@apply absolute right-0 bottom-4 flex flex-col items-center space-y-1 z-50 select-none transition-opacity duration-200 opacity-0 pointer-events-none;
|
||||
}
|
||||
|
||||
.lyric-correction-btn {
|
||||
@apply w-7 h-7 flex items-center justify-center rounded-lg bg-white dark:bg-neutral-800 border border-white/20 dark:border-neutral-700/40 shadow-md backdrop-blur-2xl cursor-pointer transition-all duration-150 text-gray-700 dark:text-gray-200 hover:bg-green-500/80 hover:text-white hover:border-green-400/60 active:scale-95 bg-opacity-40 dark:hover:bg-green-500/80 dark:hover:text-white dark:hover:border-green-400/60 dark:hover:bg-opacity-40;
|
||||
}
|
||||
|
||||
.mobile{
|
||||
.lyric-correction {
|
||||
@apply opacity-100;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -114,47 +114,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
|
||||
<!-- 移动端设置 -->
|
||||
<n-tab-pane :name="'mobile'" :tab="t('settings.lyricSettings.tabs.mobile')">
|
||||
<div class="tab-content" v-if="isMobile">
|
||||
<div class="section-title">{{ t('settings.lyricSettings.mobileLayout') }}</div>
|
||||
<n-radio-group v-model:value="config.mobileLayout" name="mobileLayout" class="mb-4">
|
||||
<n-space>
|
||||
<n-radio value="default">{{ t('settings.lyricSettings.layoutOptions.default') }}</n-radio>
|
||||
<n-radio value="ios">{{ t('settings.lyricSettings.layoutOptions.ios') }}</n-radio>
|
||||
<n-radio value="android">{{ t('settings.lyricSettings.layoutOptions.android') }}</n-radio>
|
||||
</n-space>
|
||||
</n-radio-group>
|
||||
|
||||
<div class="section-title">{{ t('settings.lyricSettings.mobileCoverStyle') }}</div>
|
||||
<n-radio-group v-model:value="config.mobileCoverStyle" name="mobileCoverStyle" class="mb-4">
|
||||
<n-space>
|
||||
<n-radio value="record">{{ t('settings.lyricSettings.coverOptions.record') }}</n-radio>
|
||||
<n-radio value="square">{{ t('settings.lyricSettings.coverOptions.square') }}</n-radio>
|
||||
<n-radio value="full">{{ t('settings.lyricSettings.coverOptions.full') }}</n-radio>
|
||||
</n-space>
|
||||
</n-radio-group>
|
||||
|
||||
<div class="slider-item">
|
||||
<span>{{ t('settings.lyricSettings.lyricLines') }}</span>
|
||||
<n-slider
|
||||
v-model:value="config.mobileShowLyricLines"
|
||||
:step="1"
|
||||
:min="1"
|
||||
:max="5"
|
||||
:marks="{
|
||||
1: '1',
|
||||
3: '3',
|
||||
5: '5'
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="mobile-unavailable">
|
||||
{{ t('settings.lyricSettings.mobileUnavailable') }}
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</div>
|
||||
</div>
|
||||
@@ -165,7 +124,6 @@ import { onMounted, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { DEFAULT_LYRIC_CONFIG, LyricConfig } from '@/types/lyric';
|
||||
import { isMobile } from '@/utils';
|
||||
|
||||
const { t } = useI18n();
|
||||
const config = ref<LyricConfig>({ ...DEFAULT_LYRIC_CONFIG });
|
||||
|
||||
@@ -21,18 +21,6 @@
|
||||
<i class="ri-arrow-down-s-line"></i>
|
||||
</div>
|
||||
|
||||
<n-popover trigger="click" placement="bottom">
|
||||
<template #trigger>
|
||||
<div
|
||||
class="control-btn absolute top-5 right-5"
|
||||
:class="{ 'pure-mode': config.pureModeEnabled }"
|
||||
>
|
||||
<i class="ri-settings-3-line"></i>
|
||||
</div>
|
||||
</template>
|
||||
<lyric-settings ref="lyricSettingsRef" />
|
||||
</n-popover>
|
||||
|
||||
<!-- 全屏歌词页面 -->
|
||||
<transition name="fade">
|
||||
<div
|
||||
@@ -102,7 +90,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-2">
|
||||
<div class="px-2 flex-1 flex flex-col justify-around">
|
||||
<!-- 歌曲信息 -->
|
||||
<div class="song-info">
|
||||
<h1 class="song-title">{{ playMusic.name }}</h1>
|
||||
@@ -128,9 +116,6 @@
|
||||
<div v-if="lrcArray.length > 0" class="lyrics-wrapper">
|
||||
<div v-for="(line, idx) in visibleLyrics" :key="idx" class="lyric-line">
|
||||
{{ line.text }}
|
||||
<div v-if="config.showTranslation && line.trText" class="translation">
|
||||
{{ line.trText }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-lyrics">
|
||||
@@ -198,7 +183,6 @@
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch, nextTick } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import LyricSettings from '@/components/lyric/LyricSettings.vue';
|
||||
import {
|
||||
allTime,
|
||||
artistList,
|
||||
@@ -459,13 +443,12 @@ const handleThumbTouchEnd = () => {
|
||||
const currentBackground = ref('');
|
||||
const animationFrame = ref<number | null>(null);
|
||||
const isDark = ref(false);
|
||||
const lyricSettingsRef = ref<InstanceType<typeof LyricSettings>>();
|
||||
const config = ref<LyricConfig>({ ...DEFAULT_LYRIC_CONFIG });
|
||||
|
||||
// 可见歌词计算
|
||||
const visibleLyrics = computed(() => {
|
||||
const centerIndex = nowIndex.value + 1;
|
||||
const numLines = config.value.mobileShowLyricLines;
|
||||
const centerIndex = nowIndex.value;
|
||||
const numLines = 3;
|
||||
const halfLines = Math.floor(numLines / 2);
|
||||
|
||||
let startIdx = centerIndex - halfLines;
|
||||
@@ -490,29 +473,6 @@ const visibleLyrics = computed(() => {
|
||||
return lrcArray.value.slice(startIdx, endIdx + 1);
|
||||
});
|
||||
|
||||
// 监听设置组件的配置变化
|
||||
watch(
|
||||
() => lyricSettingsRef.value?.config,
|
||||
(newConfig) => {
|
||||
if (newConfig) {
|
||||
config.value = newConfig;
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
|
||||
// 监听本地配置变化,保存到 localStorage
|
||||
watch(
|
||||
() => config.value,
|
||||
(newConfig) => {
|
||||
localStorage.setItem('music-full-config', JSON.stringify(newConfig));
|
||||
if (lyricSettingsRef.value) {
|
||||
lyricSettingsRef.value.config = newConfig;
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
@@ -802,6 +762,7 @@ defineExpose({
|
||||
padding-top: 100px;
|
||||
padding-bottom: 200px;
|
||||
margin-bottom: 180px; /* 确保底部留出足够空间 */
|
||||
margin-top: 90px;
|
||||
|
||||
.lyrics-padding-top {
|
||||
height: 70px;
|
||||
@@ -911,11 +872,11 @@ defineExpose({
|
||||
@apply w-10 h-10 flex items-center justify-center cursor-pointer transition-all duration-200;
|
||||
|
||||
i {
|
||||
@apply text-xl;
|
||||
@apply text-2xl;
|
||||
color: var(--text-color-primary);
|
||||
|
||||
&.favorite {
|
||||
@apply text-red-500;
|
||||
@apply text-red-500 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -930,7 +891,7 @@ defineExpose({
|
||||
@apply w-14 h-14 flex items-center justify-center cursor-pointer transition-all duration-200;
|
||||
|
||||
i {
|
||||
@apply text-2xl;
|
||||
@apply text-3xl;
|
||||
color: var(--text-color-primary);
|
||||
}
|
||||
|
||||
@@ -963,7 +924,7 @@ defineExpose({
|
||||
|
||||
// 封面样式
|
||||
.cover-container {
|
||||
@apply relative mb-6 transition-all duration-500;
|
||||
@apply relative mb-6 transition-all duration-500 border-gray-900;
|
||||
|
||||
&.style-changing {
|
||||
animation: styleChange 0.5s ease;
|
||||
@@ -974,10 +935,58 @@ defineExpose({
|
||||
}
|
||||
|
||||
&.record-style {
|
||||
@apply w-72 h-72 rounded-full overflow-hidden;
|
||||
@apply w-72 h-72 rounded-full overflow-hidden relative;
|
||||
|
||||
// 唱片外圈装饰
|
||||
&::before {
|
||||
content: '';
|
||||
@apply absolute top-0 left-0 w-full h-full rounded-full z-10;
|
||||
background: radial-gradient(circle at center,
|
||||
transparent 38%,
|
||||
rgba(0, 0, 0, 0.15) 38%,
|
||||
rgba(0, 0, 0, 0.15) 39%,
|
||||
rgba(255, 255, 255, 0.1) 39%,
|
||||
rgba(255, 255, 255, 0.1) 39.5%,
|
||||
rgba(0, 0, 0, 0.08) 39.5%,
|
||||
rgba(0, 0, 0, 0.08) 40.5%,
|
||||
rgba(0, 0, 0, 0.2) 40.5%,
|
||||
rgba(0, 0, 0, 0.2) 41.5%,
|
||||
rgba(0, 0, 0, 0.6) 41.5%,
|
||||
rgba(0, 0, 0, 0.6) 100%);
|
||||
pointer-events: none;
|
||||
animation: spin 20s linear infinite;
|
||||
animation-play-state: running;
|
||||
}
|
||||
|
||||
&.paused {
|
||||
&::before, &::after {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
}
|
||||
|
||||
.img-wrapper {
|
||||
@apply rounded-full overflow-hidden border-[40px] border-solid border-black z-0;
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
// 光泽效果
|
||||
&::after {
|
||||
content: '';
|
||||
@apply absolute top-0 left-0 w-full h-full rounded-full z-[2];
|
||||
background: linear-gradient(135deg,
|
||||
rgba(255, 255, 255, 0.05) 0%,
|
||||
rgba(255, 255, 255, 0) 50%,
|
||||
rgba(0, 0, 0, 0.05) 100%);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.cover-image {
|
||||
@apply w-full h-full rounded-full;
|
||||
@apply w-full h-full rounded-full border-[5px] border-gray-900;
|
||||
animation: spin 20s linear infinite;
|
||||
animation-play-state: running;
|
||||
}
|
||||
@@ -988,10 +997,10 @@ defineExpose({
|
||||
}
|
||||
|
||||
&.square-style {
|
||||
@apply w-72 h-72;
|
||||
@apply w-72 h-72 shadow-lg rounded-xl overflow-hidden mt-8;
|
||||
|
||||
.cover-image {
|
||||
@apply w-full h-full rounded-xl shadow-lg;
|
||||
@apply w-full h-full;
|
||||
transition: transform 0.3s ease-out;
|
||||
|
||||
&:active {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { isMobile } from '@/utils';
|
||||
import MusicFull from '@/layout/components/MusicFull.vue';
|
||||
import MusicFull from '@/components/lyric/MusicFull.vue';
|
||||
import MusicFullMobile from '@/components/lyric/MusicFullMobile.vue';
|
||||
|
||||
// 根据当前设备类型选择需要显示的组件
|
||||
@@ -113,7 +113,7 @@ import { useThrottleFn } from '@vueuse/core';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { allTime, artistList, nowTime, playMusic, sound, textColors } from '@/hooks/MusicHook';
|
||||
import MusicFullWrapper from '@/layout/components/MusicFullWrapper.vue';
|
||||
import MusicFullWrapper from '@/components/lyric/MusicFullWrapper.vue';
|
||||
import { usePlayerStore } from '@/store/modules/player';
|
||||
import { useSettingsStore } from '@/store/modules/settings';
|
||||
import { getImgUrl, secondToMinute, setAnimationClass } from '@/utils';
|
||||
|
||||
@@ -169,7 +169,7 @@ import {
|
||||
textColors
|
||||
} from '@/hooks/MusicHook';
|
||||
import { useArtist } from '@/hooks/useArtist';
|
||||
import MusicFullWrapper from '@/layout/components/MusicFullWrapper.vue';
|
||||
import MusicFullWrapper from '@/components/lyric/MusicFullWrapper.vue';
|
||||
import { audioService } from '@/services/audioService';
|
||||
import {
|
||||
isBilibiliIdMatch,
|
||||
|
||||
@@ -203,7 +203,7 @@ const restartApp = () => {
|
||||
};
|
||||
|
||||
const toLogin = () => {
|
||||
router.push('/login');
|
||||
router.push('/user');
|
||||
};
|
||||
|
||||
// 页面初始化
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
<template>
|
||||
<div class="lrc-full">
|
||||
{{ lrcIndex }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
lrcList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
lrcIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
lrcTime: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -84,7 +84,8 @@ const layoutRouter = [
|
||||
title: '设置',
|
||||
icon: 'ri-settings-3-fill',
|
||||
keepAlive: true,
|
||||
noScroll: true
|
||||
noScroll: true,
|
||||
back: true
|
||||
},
|
||||
component: () => import('@/views/set/index.vue')
|
||||
}
|
||||
|
||||
@@ -29,22 +29,11 @@ const loginRouter = {
|
||||
component: () => import('@/views/login/index.vue')
|
||||
};
|
||||
|
||||
const setRouter = {
|
||||
path: '/set',
|
||||
name: 'set',
|
||||
meta: {
|
||||
keepAlive: true,
|
||||
title: '设置',
|
||||
icon: 'icon-Home'
|
||||
},
|
||||
component: () => import('@/views/set/index.vue')
|
||||
};
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
component: AppLayout,
|
||||
children: [...homeRouter, loginRouter, setRouter, ...otherRouter]
|
||||
children: [...homeRouter, loginRouter, ...otherRouter]
|
||||
},
|
||||
{
|
||||
path: '/lyric',
|
||||
|
||||
@@ -783,4 +783,10 @@ const handleVirtualScroll = (e: any) => {
|
||||
@apply mb-2 bg-light-100 bg-opacity-30 dark:bg-dark-100 dark:bg-opacity-20 rounded-3xl;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile {
|
||||
.songs-toolbar{
|
||||
@apply mb-0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user