mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-24 16:27:23 +08:00
Merge branch 'algerkong:main' into feature
This commit is contained in:
+7
-1
@@ -28,4 +28,10 @@ out
|
|||||||
|
|
||||||
resources/android/**/*
|
resources/android/**/*
|
||||||
|
|
||||||
.cursor
|
.cursor
|
||||||
|
|
||||||
|
.auto-imports.d.ts
|
||||||
|
.components.d.ts
|
||||||
|
|
||||||
|
src/renderer/auto-imports.d.ts
|
||||||
|
src/renderer/components.d.ts
|
||||||
Vendored
-90
@@ -1,90 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
/* prettier-ignore */
|
|
||||||
// @ts-nocheck
|
|
||||||
// noinspection JSUnusedGlobalSymbols
|
|
||||||
// Generated by unplugin-auto-import
|
|
||||||
// biome-ignore lint: disable
|
|
||||||
export {}
|
|
||||||
declare global {
|
|
||||||
const EffectScope: (typeof import('vue'))['EffectScope'];
|
|
||||||
const computed: (typeof import('vue'))['computed'];
|
|
||||||
const createApp: (typeof import('vue'))['createApp'];
|
|
||||||
const customRef: (typeof import('vue'))['customRef'];
|
|
||||||
const defineAsyncComponent: (typeof import('vue'))['defineAsyncComponent'];
|
|
||||||
const defineComponent: (typeof import('vue'))['defineComponent'];
|
|
||||||
const effectScope: (typeof import('vue'))['effectScope'];
|
|
||||||
const getCurrentInstance: (typeof import('vue'))['getCurrentInstance'];
|
|
||||||
const getCurrentScope: (typeof import('vue'))['getCurrentScope'];
|
|
||||||
const h: (typeof import('vue'))['h'];
|
|
||||||
const inject: (typeof import('vue'))['inject'];
|
|
||||||
const isProxy: (typeof import('vue'))['isProxy'];
|
|
||||||
const isReactive: (typeof import('vue'))['isReactive'];
|
|
||||||
const isReadonly: (typeof import('vue'))['isReadonly'];
|
|
||||||
const isRef: (typeof import('vue'))['isRef'];
|
|
||||||
const markRaw: (typeof import('vue'))['markRaw'];
|
|
||||||
const nextTick: (typeof import('vue'))['nextTick'];
|
|
||||||
const onActivated: (typeof import('vue'))['onActivated'];
|
|
||||||
const onBeforeMount: (typeof import('vue'))['onBeforeMount'];
|
|
||||||
const onBeforeUnmount: (typeof import('vue'))['onBeforeUnmount'];
|
|
||||||
const onBeforeUpdate: (typeof import('vue'))['onBeforeUpdate'];
|
|
||||||
const onDeactivated: (typeof import('vue'))['onDeactivated'];
|
|
||||||
const onErrorCaptured: (typeof import('vue'))['onErrorCaptured'];
|
|
||||||
const onMounted: (typeof import('vue'))['onMounted'];
|
|
||||||
const onRenderTracked: (typeof import('vue'))['onRenderTracked'];
|
|
||||||
const onRenderTriggered: (typeof import('vue'))['onRenderTriggered'];
|
|
||||||
const onScopeDispose: (typeof import('vue'))['onScopeDispose'];
|
|
||||||
const onServerPrefetch: (typeof import('vue'))['onServerPrefetch'];
|
|
||||||
const onUnmounted: (typeof import('vue'))['onUnmounted'];
|
|
||||||
const onUpdated: (typeof import('vue'))['onUpdated'];
|
|
||||||
const onWatcherCleanup: (typeof import('vue'))['onWatcherCleanup'];
|
|
||||||
const provide: (typeof import('vue'))['provide'];
|
|
||||||
const reactive: (typeof import('vue'))['reactive'];
|
|
||||||
const readonly: (typeof import('vue'))['readonly'];
|
|
||||||
const ref: (typeof import('vue'))['ref'];
|
|
||||||
const resolveComponent: (typeof import('vue'))['resolveComponent'];
|
|
||||||
const shallowReactive: (typeof import('vue'))['shallowReactive'];
|
|
||||||
const shallowReadonly: (typeof import('vue'))['shallowReadonly'];
|
|
||||||
const shallowRef: (typeof import('vue'))['shallowRef'];
|
|
||||||
const toRaw: (typeof import('vue'))['toRaw'];
|
|
||||||
const toRef: (typeof import('vue'))['toRef'];
|
|
||||||
const toRefs: (typeof import('vue'))['toRefs'];
|
|
||||||
const toValue: (typeof import('vue'))['toValue'];
|
|
||||||
const triggerRef: (typeof import('vue'))['triggerRef'];
|
|
||||||
const unref: (typeof import('vue'))['unref'];
|
|
||||||
const useAttrs: (typeof import('vue'))['useAttrs'];
|
|
||||||
const useCssModule: (typeof import('vue'))['useCssModule'];
|
|
||||||
const useCssVars: (typeof import('vue'))['useCssVars'];
|
|
||||||
const useDialog: (typeof import('naive-ui'))['useDialog'];
|
|
||||||
const useId: (typeof import('vue'))['useId'];
|
|
||||||
const useLoadingBar: (typeof import('naive-ui'))['useLoadingBar'];
|
|
||||||
const useMessage: (typeof import('naive-ui'))['useMessage'];
|
|
||||||
const useModel: (typeof import('vue'))['useModel'];
|
|
||||||
const useNotification: (typeof import('naive-ui'))['useNotification'];
|
|
||||||
const useSlots: (typeof import('vue'))['useSlots'];
|
|
||||||
const useTemplateRef: (typeof import('vue'))['useTemplateRef'];
|
|
||||||
const watch: (typeof import('vue'))['watch'];
|
|
||||||
const watchEffect: (typeof import('vue'))['watchEffect'];
|
|
||||||
const watchPostEffect: (typeof import('vue'))['watchPostEffect'];
|
|
||||||
const watchSyncEffect: (typeof import('vue'))['watchSyncEffect'];
|
|
||||||
}
|
|
||||||
// for type re-export
|
|
||||||
declare global {
|
|
||||||
// @ts-ignore
|
|
||||||
export type {
|
|
||||||
Component,
|
|
||||||
ComponentPublicInstance,
|
|
||||||
ComputedRef,
|
|
||||||
DirectiveBinding,
|
|
||||||
ExtractDefaultPropTypes,
|
|
||||||
ExtractPropTypes,
|
|
||||||
ExtractPublicPropTypes,
|
|
||||||
InjectionKey,
|
|
||||||
PropType,
|
|
||||||
Ref,
|
|
||||||
MaybeRef,
|
|
||||||
MaybeRefOrGetter,
|
|
||||||
VNode,
|
|
||||||
WritableComputedRef
|
|
||||||
} from 'vue';
|
|
||||||
import('vue');
|
|
||||||
}
|
|
||||||
Vendored
-33
@@ -1,33 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
// @ts-nocheck
|
|
||||||
// Generated by unplugin-vue-components
|
|
||||||
// Read more: https://github.com/vuejs/core/pull/3399
|
|
||||||
export {};
|
|
||||||
|
|
||||||
/* prettier-ignore */
|
|
||||||
declare module 'vue' {
|
|
||||||
export interface GlobalComponents {
|
|
||||||
NAvatar: typeof import('naive-ui')['NAvatar']
|
|
||||||
NButton: typeof import('naive-ui')['NButton']
|
|
||||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
|
||||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
|
||||||
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
|
|
||||||
NDrawer: typeof import('naive-ui')['NDrawer']
|
|
||||||
NDropdown: typeof import('naive-ui')['NDropdown']
|
|
||||||
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
|
||||||
NEmpty: typeof import('naive-ui')['NEmpty']
|
|
||||||
NImage: typeof import('naive-ui')['NImage']
|
|
||||||
NInput: typeof import('naive-ui')['NInput']
|
|
||||||
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
|
||||||
NLayout: typeof import('naive-ui')['NLayout']
|
|
||||||
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
|
||||||
NModal: typeof import('naive-ui')['NModal']
|
|
||||||
NPopover: typeof import('naive-ui')['NPopover']
|
|
||||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
|
||||||
NSlider: typeof import('naive-ui')['NSlider']
|
|
||||||
NSpin: typeof import('naive-ui')['NSpin']
|
|
||||||
NSwitch: typeof import('naive-ui')['NSwitch']
|
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-75
@@ -1,75 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
/* prettier-ignore */
|
|
||||||
// @ts-nocheck
|
|
||||||
// noinspection JSUnusedGlobalSymbols
|
|
||||||
// Generated by unplugin-auto-import
|
|
||||||
// biome-ignore lint: disable
|
|
||||||
export {}
|
|
||||||
declare global {
|
|
||||||
const EffectScope: typeof import('vue')['EffectScope']
|
|
||||||
const computed: typeof import('vue')['computed']
|
|
||||||
const createApp: typeof import('vue')['createApp']
|
|
||||||
const customRef: typeof import('vue')['customRef']
|
|
||||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
|
||||||
const defineComponent: typeof import('vue')['defineComponent']
|
|
||||||
const effectScope: typeof import('vue')['effectScope']
|
|
||||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
|
||||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
|
||||||
const h: typeof import('vue')['h']
|
|
||||||
const inject: typeof import('vue')['inject']
|
|
||||||
const isProxy: typeof import('vue')['isProxy']
|
|
||||||
const isReactive: typeof import('vue')['isReactive']
|
|
||||||
const isReadonly: typeof import('vue')['isReadonly']
|
|
||||||
const isRef: typeof import('vue')['isRef']
|
|
||||||
const markRaw: typeof import('vue')['markRaw']
|
|
||||||
const nextTick: typeof import('vue')['nextTick']
|
|
||||||
const onActivated: typeof import('vue')['onActivated']
|
|
||||||
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
|
||||||
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
|
||||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
|
||||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
|
||||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
|
||||||
const onMounted: typeof import('vue')['onMounted']
|
|
||||||
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
|
||||||
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
|
||||||
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
|
||||||
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
|
||||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
|
||||||
const onUpdated: typeof import('vue')['onUpdated']
|
|
||||||
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
|
|
||||||
const provide: typeof import('vue')['provide']
|
|
||||||
const reactive: typeof import('vue')['reactive']
|
|
||||||
const readonly: typeof import('vue')['readonly']
|
|
||||||
const ref: typeof import('vue')['ref']
|
|
||||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
|
||||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
|
||||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
|
||||||
const shallowRef: typeof import('vue')['shallowRef']
|
|
||||||
const toRaw: typeof import('vue')['toRaw']
|
|
||||||
const toRef: typeof import('vue')['toRef']
|
|
||||||
const toRefs: typeof import('vue')['toRefs']
|
|
||||||
const toValue: typeof import('vue')['toValue']
|
|
||||||
const triggerRef: typeof import('vue')['triggerRef']
|
|
||||||
const unref: typeof import('vue')['unref']
|
|
||||||
const useAttrs: typeof import('vue')['useAttrs']
|
|
||||||
const useCssModule: typeof import('vue')['useCssModule']
|
|
||||||
const useCssVars: typeof import('vue')['useCssVars']
|
|
||||||
const useDialog: typeof import('naive-ui')['useDialog']
|
|
||||||
const useId: typeof import('vue')['useId']
|
|
||||||
const useLoadingBar: typeof import('naive-ui')['useLoadingBar']
|
|
||||||
const useMessage: typeof import('naive-ui')['useMessage']
|
|
||||||
const useModel: typeof import('vue')['useModel']
|
|
||||||
const useNotification: typeof import('naive-ui')['useNotification']
|
|
||||||
const useSlots: typeof import('vue')['useSlots']
|
|
||||||
const useTemplateRef: typeof import('vue')['useTemplateRef']
|
|
||||||
const watch: typeof import('vue')['watch']
|
|
||||||
const watchEffect: typeof import('vue')['watchEffect']
|
|
||||||
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
|
||||||
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
|
||||||
}
|
|
||||||
// for type re-export
|
|
||||||
declare global {
|
|
||||||
// @ts-ignore
|
|
||||||
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
|
||||||
import('vue')
|
|
||||||
}
|
|
||||||
Vendored
-57
@@ -1,57 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
// @ts-nocheck
|
|
||||||
// Generated by unplugin-vue-components
|
|
||||||
// Read more: https://github.com/vuejs/core/pull/3399
|
|
||||||
// biome-ignore lint: disable
|
|
||||||
export {}
|
|
||||||
|
|
||||||
/* prettier-ignore */
|
|
||||||
declare module 'vue' {
|
|
||||||
export interface GlobalComponents {
|
|
||||||
NAlert: typeof import('naive-ui')['NAlert']
|
|
||||||
NAvatar: typeof import('naive-ui')['NAvatar']
|
|
||||||
NBadge: typeof import('naive-ui')['NBadge']
|
|
||||||
NButton: typeof import('naive-ui')['NButton']
|
|
||||||
NButtonGroup: typeof import('naive-ui')['NButtonGroup']
|
|
||||||
NCarousel: typeof import('naive-ui')['NCarousel']
|
|
||||||
NCarouselItem: typeof import('naive-ui')['NCarouselItem']
|
|
||||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
|
||||||
NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup']
|
|
||||||
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
|
|
||||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
|
||||||
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
|
|
||||||
NDrawer: typeof import('naive-ui')['NDrawer']
|
|
||||||
NDrawerContent: typeof import('naive-ui')['NDrawerContent']
|
|
||||||
NDropdown: typeof import('naive-ui')['NDropdown']
|
|
||||||
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
|
||||||
NEmpty: typeof import('naive-ui')['NEmpty']
|
|
||||||
NForm: typeof import('naive-ui')['NForm']
|
|
||||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
|
||||||
NGrid: typeof import('naive-ui')['NGrid']
|
|
||||||
NGridItem: typeof import('naive-ui')['NGridItem']
|
|
||||||
NIcon: typeof import('naive-ui')['NIcon']
|
|
||||||
NImage: typeof import('naive-ui')['NImage']
|
|
||||||
NInput: typeof import('naive-ui')['NInput']
|
|
||||||
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
|
||||||
NLayout: typeof import('naive-ui')['NLayout']
|
|
||||||
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
|
||||||
NModal: typeof import('naive-ui')['NModal']
|
|
||||||
NPopover: typeof import('naive-ui')['NPopover']
|
|
||||||
NRadio: typeof import('naive-ui')['NRadio']
|
|
||||||
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
|
|
||||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
|
||||||
NSelect: typeof import('naive-ui')['NSelect']
|
|
||||||
NSlider: typeof import('naive-ui')['NSlider']
|
|
||||||
NSpace: typeof import('naive-ui')['NSpace']
|
|
||||||
NSpin: typeof import('naive-ui')['NSpin']
|
|
||||||
NSwitch: typeof import('naive-ui')['NSwitch']
|
|
||||||
NTabPane: typeof import('naive-ui')['NTabPane']
|
|
||||||
NTabs: typeof import('naive-ui')['NTabs']
|
|
||||||
NTag: typeof import('naive-ui')['NTag']
|
|
||||||
NText: typeof import('naive-ui')['NText']
|
|
||||||
NTooltip: typeof import('naive-ui')['NTooltip']
|
|
||||||
NVirtualList: typeof import('naive-ui')['NVirtualList']
|
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="sortedDonors.length > 8" class="expand-button">
|
<div v-if="donors.length > 8" class="expand-button">
|
||||||
<n-button text @click="toggleExpand">
|
<n-button text @click="toggleExpand">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<i :class="isExpanded ? 'ri-arrow-up-s-line' : 'ri-arrow-down-s-line'"></i>
|
<i :class="isExpanded ? 'ri-arrow-up-s-line' : 'ri-arrow-down-s-line'"></i>
|
||||||
@@ -150,18 +150,13 @@ onActivated(() => {
|
|||||||
fetchDonors();
|
fetchDonors();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 只按金额排序的捐赠列表
|
|
||||||
const sortedDonors = computed(() => {
|
|
||||||
return [...donors.value].sort((a, b) => b.amount - a.amount);
|
|
||||||
});
|
|
||||||
|
|
||||||
const isExpanded = ref(false);
|
const isExpanded = ref(false);
|
||||||
|
|
||||||
const displayDonors = computed(() => {
|
const displayDonors = computed(() => {
|
||||||
if (isExpanded.value) {
|
if (isExpanded.value) {
|
||||||
return sortedDonors.value;
|
return donors.value;
|
||||||
}
|
}
|
||||||
return sortedDonors.value.slice(0, 8);
|
return donors.value.slice(0, 8);
|
||||||
});
|
});
|
||||||
|
|
||||||
const toggleExpand = () => {
|
const toggleExpand = () => {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
:z-index="9998"
|
:z-index="9998"
|
||||||
>
|
>
|
||||||
|
|
||||||
<div id="mobile-drawer-target" :class="[config.theme, `cover-style-${config.mobileCoverStyle}`]">
|
<div id="mobile-drawer-target" :class="[config.theme, `cover-style-${config.mobileCoverStyle}`, {'is-landscape': isLandscape}]">
|
||||||
<!-- 顶部控制按钮 -->
|
<!-- 顶部控制按钮 -->
|
||||||
<div v-if="playMusic?.playLoading" class="loading-overlay">
|
<div v-if="playMusic?.playLoading" class="loading-overlay">
|
||||||
<i class="ri-loader-4-line loading-icon"></i>
|
<i class="ri-loader-4-line loading-icon"></i>
|
||||||
@@ -21,10 +21,10 @@
|
|||||||
<i class="ri-arrow-down-s-line"></i>
|
<i class="ri-arrow-down-s-line"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 全屏歌词页面 -->
|
<!-- 全屏歌词页面 - 竖屏模式下 -->
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<div
|
<div
|
||||||
v-if="showFullLyrics"
|
v-if="showFullLyrics && !isLandscape"
|
||||||
class="fullscreen-lyrics"
|
class="fullscreen-lyrics"
|
||||||
:class="config.theme"
|
:class="config.theme"
|
||||||
>
|
>
|
||||||
@@ -64,9 +64,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
<!-- 主要内容区域 -->
|
<!-- 主要内容区域 - 竖屏模式下的普通布局 -->
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<div v-if="!showFullLyrics" class="ios-layout-container">
|
<div v-if="!showFullLyrics && !isLandscape" class="ios-layout-container">
|
||||||
<!-- 封面区域 -->
|
<!-- 封面区域 -->
|
||||||
<div
|
<div
|
||||||
class="cover-container"
|
class="cover-container"
|
||||||
@@ -126,8 +126,121 @@
|
|||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
<!-- 统一的控制区域 -->
|
<!-- 横屏模式布局 -->
|
||||||
<div class="unified-controls" :class="{ 'fullscreen-mode': showFullLyrics }">
|
<div v-if="isLandscape" class="landscape-layout">
|
||||||
|
<!-- 左侧封面和进度条 -->
|
||||||
|
<div class="landscape-left-section">
|
||||||
|
<div
|
||||||
|
class="landscape-cover-container"
|
||||||
|
:class="{
|
||||||
|
'record-style': config.mobileCoverStyle === 'record',
|
||||||
|
'square-style': config.mobileCoverStyle === 'square',
|
||||||
|
'full-style': config.mobileCoverStyle === 'full',
|
||||||
|
'paused': !play
|
||||||
|
}"
|
||||||
|
@click="cycleCoverStyle"
|
||||||
|
>
|
||||||
|
<div class="img-wrapper">
|
||||||
|
<n-image
|
||||||
|
:src="getImgUrl(playMusic?.picUrl, '500y500')"
|
||||||
|
lazy
|
||||||
|
preview-disabled
|
||||||
|
class="cover-image"
|
||||||
|
:class="{ 'full-blend': config.mobileCoverStyle === 'full' }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 音频频谱可视化 -->
|
||||||
|
<div class="spectrum-container">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 左侧进度条 -->
|
||||||
|
<div class="landscape-progress-container">
|
||||||
|
<div class="time-info">
|
||||||
|
<span class="current-time">{{ secondToMinute(nowTime) }}</span>
|
||||||
|
<span class="total-time">{{ secondToMinute(allTime) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="apple-style-progress" @click="handleProgressBarClick">
|
||||||
|
<div class="progress-track">
|
||||||
|
<div
|
||||||
|
class="progress-fill"
|
||||||
|
:style="{ width: `${(nowTime / Math.max(1, allTime)) * 100}%` }"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
class="progress-thumb"
|
||||||
|
:class="{ 'active': isThumbDragging }"
|
||||||
|
:style="{ left: `${(nowTime / Math.max(1, allTime)) * 100}%` }"
|
||||||
|
@touchstart="handleThumbTouchStart"
|
||||||
|
@touchmove="handleThumbTouchMove"
|
||||||
|
@touchend="handleThumbTouchEnd"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧歌词区域 -->
|
||||||
|
<div class="landscape-lyrics-section">
|
||||||
|
<!-- 歌曲信息放置在顶部 -->
|
||||||
|
<div class="landscape-song-info">
|
||||||
|
<h1 class="song-title">{{ playMusic.name }}</h1>
|
||||||
|
<p class="song-artist">
|
||||||
|
<span
|
||||||
|
v-for="(item, index) in artistList"
|
||||||
|
:key="index"
|
||||||
|
class="artist-name"
|
||||||
|
@click="handleArtistClick(item.id)"
|
||||||
|
>
|
||||||
|
{{ item.name }}
|
||||||
|
{{ index < artistList.length - 1 ? ' / ' : '' }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 歌词滚动区域 -->
|
||||||
|
<div
|
||||||
|
ref="landscapeLyricsRef"
|
||||||
|
class="landscape-lyrics-scroller"
|
||||||
|
@touchstart="handleTouchStart"
|
||||||
|
@touchmove="handleTouchMove"
|
||||||
|
@touchend="handleTouchEnd"
|
||||||
|
@scroll="handleScroll"
|
||||||
|
>
|
||||||
|
<div class="lyrics-padding-top"></div>
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in lrcArray"
|
||||||
|
:key="index"
|
||||||
|
:id="`landscape-lyric-line-${index}`"
|
||||||
|
class="lyric-line"
|
||||||
|
:class="{ 'now-text': index === nowIndex, 'hover-text': item.text }"
|
||||||
|
@click="jumpToLyricTime(index)"
|
||||||
|
>
|
||||||
|
<span :style="getLrcStyle(index)">{{ item.text }}</span>
|
||||||
|
<div v-if="config.showTranslation && item.trText" class="translation">
|
||||||
|
{{ item.trText }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="lyrics-padding-bottom"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右下角控制按钮 -->
|
||||||
|
<div class="landscape-main-controls">
|
||||||
|
<div class="main-button prev" @click="prevSong">
|
||||||
|
<i class="ri-skip-back-fill"></i>
|
||||||
|
</div>
|
||||||
|
<div class="main-button play-pause" @click="togglePlay">
|
||||||
|
<i :class="playIcon"></i>
|
||||||
|
</div>
|
||||||
|
<div class="main-button next" @click="nextSong">
|
||||||
|
<i class="ri-skip-forward-fill"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 竖屏模式的控制区域 -->
|
||||||
|
<div v-if="!isLandscape" class="unified-controls" :class="{ 'fullscreen-mode': showFullLyrics }">
|
||||||
<!-- 进度条 (苹果风格) -->
|
<!-- 进度条 (苹果风格) -->
|
||||||
<div class="progress-container">
|
<div class="progress-container">
|
||||||
<div class="time-info">
|
<div class="time-info">
|
||||||
@@ -182,6 +295,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onBeforeUnmount, onMounted, ref, watch, nextTick } from 'vue';
|
import { computed, onBeforeUnmount, onMounted, ref, watch, nextTick } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useWindowSize } from '@vueuse/core';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
allTime,
|
allTime,
|
||||||
@@ -243,6 +357,23 @@ 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 { width, height } = useWindowSize();
|
||||||
|
const isLandscape = computed(() => width.value > height.value);
|
||||||
|
const landscapeLyricsRef = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
// 监听横屏变化
|
||||||
|
watch(isLandscape, (newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
// 横屏模式下,确保歌词容器可见并滚动到当前歌词
|
||||||
|
nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
scrollToCurrentLyric(true, landscapeLyricsRef.value);
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 显示全屏歌词
|
// 显示全屏歌词
|
||||||
const showFullLyricScreen = () => {
|
const showFullLyricScreen = () => {
|
||||||
showFullLyrics.value = true;
|
showFullLyrics.value = true;
|
||||||
@@ -270,24 +401,26 @@ const closeFullLyrics = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 滚动到当前歌词,添加错误处理和日志
|
// 滚动到当前歌词,添加错误处理和日志
|
||||||
const scrollToCurrentLyric = (immediate = false) => {
|
const scrollToCurrentLyric = (immediate = false, customScrollerRef?: HTMLElement | null) => {
|
||||||
try {
|
try {
|
||||||
if (!lyricsScrollerRef.value || !isAutoScrollEnabled.value || isTouchScrolling.value) return;
|
const scrollerRef = customScrollerRef || lyricsScrollerRef.value;
|
||||||
|
if (!scrollerRef || !isAutoScrollEnabled.value || isTouchScrolling.value) return;
|
||||||
|
|
||||||
const activeEl = document.getElementById(`lyric-line-${nowIndex.value}`);
|
const prefix = customScrollerRef ? 'landscape-' : '';
|
||||||
|
const activeEl = document.getElementById(`${prefix}lyric-line-${nowIndex.value}`);
|
||||||
if (!activeEl) {
|
if (!activeEl) {
|
||||||
console.log('找不到当前歌词元素');
|
console.log('找不到当前歌词元素');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const containerRect = lyricsScrollerRef.value.getBoundingClientRect();
|
const containerRect = scrollerRef.getBoundingClientRect();
|
||||||
const lineRect = activeEl.getBoundingClientRect();
|
const lineRect = activeEl.getBoundingClientRect();
|
||||||
|
|
||||||
const scrollTop = lyricsScrollerRef.value.scrollTop + (lineRect.top - containerRect.top) - (containerRect.height / 2) + (lineRect.height / 2);
|
const scrollTop = scrollerRef.scrollTop + (lineRect.top - containerRect.top) - (containerRect.height / 2) + (lineRect.height / 2);
|
||||||
|
|
||||||
console.log('滚动到位置:', scrollTop);
|
console.log('滚动到位置:', scrollTop);
|
||||||
|
|
||||||
lyricsScrollerRef.value.scrollTo({
|
scrollerRef.scrollTo({
|
||||||
top: scrollTop,
|
top: scrollTop,
|
||||||
behavior: immediate ? 'auto' : 'smooth'
|
behavior: immediate ? 'auto' : 'smooth'
|
||||||
});
|
});
|
||||||
@@ -302,6 +435,10 @@ watch(nowIndex, () => {
|
|||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
scrollToCurrentLyric();
|
scrollToCurrentLyric();
|
||||||
});
|
});
|
||||||
|
} else if (isLandscape.value && !showFullLyrics.value) {
|
||||||
|
nextTick(() => {
|
||||||
|
scrollToCurrentLyric(false, landscapeLyricsRef.value);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -678,10 +815,6 @@ const getLrcStyle = (index: number) => {
|
|||||||
color: colors.primary
|
color: colors.primary
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
config
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -689,6 +822,278 @@ defineExpose({
|
|||||||
@apply top-0 left-0 absolute overflow-hidden flex flex-col w-full h-full;
|
@apply top-0 left-0 absolute overflow-hidden flex flex-col w-full h-full;
|
||||||
animation-duration: 300ms;
|
animation-duration: 300ms;
|
||||||
|
|
||||||
|
// 横屏模式布局
|
||||||
|
&.is-landscape {
|
||||||
|
.landscape-layout {
|
||||||
|
@apply flex flex-row w-full h-full overflow-hidden px-8;
|
||||||
|
|
||||||
|
// 左侧区域 - 封面和进度条
|
||||||
|
.landscape-left-section {
|
||||||
|
@apply h-full flex flex-col items-center justify-center pt-6 pb-6 px-3 relative;
|
||||||
|
width: 40%;
|
||||||
|
min-width: 380px;
|
||||||
|
max-width: 480px;
|
||||||
|
|
||||||
|
// 封面
|
||||||
|
.landscape-cover-container {
|
||||||
|
@apply flex-shrink-0 mx-auto mb-8;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 280px;
|
||||||
|
min-width: 200px;
|
||||||
|
|
||||||
|
&.record-style {
|
||||||
|
@apply rounded-full overflow-hidden relative;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
|
||||||
|
// 唱片外圈装饰
|
||||||
|
&::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-[20px] 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 border-[3px] border-gray-900;
|
||||||
|
animation: spin 20s linear infinite;
|
||||||
|
animation-play-state: running;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.paused .cover-image {
|
||||||
|
animation-play-state: paused;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.square-style {
|
||||||
|
@apply shadow-lg rounded-xl overflow-hidden;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
|
||||||
|
.cover-image {
|
||||||
|
@apply w-full h-full;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.full-style {
|
||||||
|
@apply relative;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
|
||||||
|
.cover-image {
|
||||||
|
@apply w-full h-auto shadow-lg rounded-xl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 频谱容器
|
||||||
|
.spectrum-container {
|
||||||
|
@apply w-full max-w-md mb-3 px-2;
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 左侧进度条
|
||||||
|
.landscape-progress-container {
|
||||||
|
@apply mt-0 mb-2 px-2 w-full max-w-md;
|
||||||
|
|
||||||
|
.time-info {
|
||||||
|
@apply flex justify-between items-center mb-2;
|
||||||
|
|
||||||
|
.current-time, .total-time {
|
||||||
|
@apply text-sm;
|
||||||
|
color: var(--text-color-primary);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.apple-style-progress {
|
||||||
|
@apply relative h-8 flex items-center cursor-pointer;
|
||||||
|
|
||||||
|
.progress-track {
|
||||||
|
@apply relative w-full h-2 bg-white bg-opacity-20 rounded-full;
|
||||||
|
|
||||||
|
.progress-fill {
|
||||||
|
@apply absolute top-0 left-0 h-full bg-white rounded-full;
|
||||||
|
box-shadow: 0 0 8px rgba(255, 255, 255, 0.5);
|
||||||
|
z-index: 1;
|
||||||
|
transition: width 0.1s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-thumb {
|
||||||
|
@apply absolute top-1/2 -translate-y-1/2 -translate-x-1/2 w-5 h-5 rounded-full bg-white;
|
||||||
|
box-shadow: 0 0 8px rgba(255, 255, 255, 0.6);
|
||||||
|
z-index: 2;
|
||||||
|
transition: transform 0.15s ease-out;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
transform: translate(-50%, -50%) scale(1.3);
|
||||||
|
box-shadow: 0 0 12px rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translate(-50%, -50%) scale(1.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 右侧区域 - 歌词和主要控制按钮
|
||||||
|
.landscape-lyrics-section {
|
||||||
|
@apply h-full flex-1 flex flex-col relative;
|
||||||
|
|
||||||
|
// 歌曲信息 - 现在在歌词顶部
|
||||||
|
.landscape-song-info {
|
||||||
|
@apply flex flex-col pt-5 px-6 z-10;
|
||||||
|
|
||||||
|
.song-title {
|
||||||
|
@apply text-2xl font-bold mb-1 line-clamp-1;
|
||||||
|
color: var(--text-color-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
.song-artist {
|
||||||
|
@apply text-base font-medium line-clamp-1;
|
||||||
|
color: var(--text-color-primary);
|
||||||
|
opacity: 0.9;
|
||||||
|
|
||||||
|
.artist-name {
|
||||||
|
@apply cursor-pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
@apply underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 歌词滚动区域
|
||||||
|
.landscape-lyrics-scroller {
|
||||||
|
@apply h-full w-full overflow-y-auto px-6 pt-24 pb-24;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
mask-image: linear-gradient(to bottom, transparent 5%, black 15%, black 85%, transparent 95%);
|
||||||
|
-webkit-mask-image: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
transparent 5%,
|
||||||
|
black 15%,
|
||||||
|
black 85%,
|
||||||
|
transparent 95%
|
||||||
|
);
|
||||||
|
|
||||||
|
.lyrics-padding-top {
|
||||||
|
height: 30px;
|
||||||
|
min-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lyrics-padding-bottom {
|
||||||
|
height: 100px;
|
||||||
|
min-height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lyric-line {
|
||||||
|
@apply px-4 py-3 cursor-pointer text-left transition-all duration-300;
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: var(--lyric-letter-spacing, 0);
|
||||||
|
line-height: var(--lyric-line-height, 1.6);
|
||||||
|
color: var(--text-color-primary);
|
||||||
|
opacity: 0.8;
|
||||||
|
|
||||||
|
span {
|
||||||
|
background-clip: text !important;
|
||||||
|
-webkit-background-clip: text !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.now-text {
|
||||||
|
@apply font-bold text-3xl py-4;
|
||||||
|
color: var(--text-color-active);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.translation {
|
||||||
|
@apply font-normal opacity-70 mt-1 text-base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主要控制按钮 - 右下角
|
||||||
|
.landscape-main-controls {
|
||||||
|
@apply fixed bottom-6 right-6 flex items-center z-10;
|
||||||
|
|
||||||
|
.main-button {
|
||||||
|
@apply mx-2 flex items-center justify-center cursor-pointer transition-all duration-200 rounded-full;
|
||||||
|
width: 54px;
|
||||||
|
height: 54px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
|
||||||
|
i {
|
||||||
|
@apply text-2xl;
|
||||||
|
color: var(--text-color-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.play-pause {
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.25);
|
||||||
|
|
||||||
|
i {
|
||||||
|
@apply text-4xl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.3);
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.control-btn {
|
.control-btn {
|
||||||
@apply w-9 h-9 flex items-center justify-center rounded cursor-pointer transition-all duration-300 z-[9999];
|
@apply w-9 h-9 flex items-center justify-center rounded cursor-pointer transition-all duration-300 z-[9999];
|
||||||
background: rgba(142, 142, 142, 0.192);
|
background: rgba(142, 142, 142, 0.192);
|
||||||
@@ -810,6 +1215,10 @@ defineExpose({
|
|||||||
height: 210px;
|
height: 210px;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
|
|
||||||
|
&.landscape-mode {
|
||||||
|
background: linear-gradient(to right, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
.progress-container {
|
.progress-container {
|
||||||
@apply w-full mb-6;
|
@apply w-full mb-6;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
@@ -1010,7 +1419,7 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.full-style {
|
&.full-style {
|
||||||
@apply w-full max-h-[50vh] relative;
|
@apply w-full max-h-[50vh] relative overflow-hidden;
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: '';
|
content: '';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="componentToUse" v-bind="$attrs" />
|
<component :is="componentToUse" v-bind="$attrs" ref="musicFullRef" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -12,4 +12,10 @@ import MusicFullMobile from '@/components/lyric/MusicFullMobile.vue';
|
|||||||
const componentToUse = computed(() => {
|
const componentToUse = computed(() => {
|
||||||
return isMobile.value ? MusicFullMobile : MusicFull;
|
return isMobile.value ? MusicFullMobile : MusicFull;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const musicFullRef = ref<InstanceType<typeof MusicFull>>();
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
musicFullRef
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
:class="[
|
:class="[
|
||||||
setAnimationClass('animate__bounceInUp'),
|
setAnimationClass('animate__bounceInUp'),
|
||||||
musicFullVisible ? 'play-bar-opcity' : '',
|
musicFullVisible ? 'play-bar-opcity' : '',
|
||||||
musicFullVisible && MusicFullRef?.config?.hidePlayBar
|
musicFullVisible && MusicFullRef?.musicFullRef?.config?.hidePlayBar
|
||||||
? 'animate__animated animate__slideOutDown'
|
? 'animate__animated animate__slideOutDown'
|
||||||
: ''
|
: ''
|
||||||
]"
|
]"
|
||||||
|
|||||||
@@ -873,24 +873,6 @@ export const initAudioListeners = async () => {
|
|||||||
if (finalSound) {
|
if (finalSound) {
|
||||||
// 更新全局 sound 引用
|
// 更新全局 sound 引用
|
||||||
sound.value = finalSound;
|
sound.value = finalSound;
|
||||||
|
|
||||||
// 如果当前处于播放状态,启动进度更新
|
|
||||||
if (playerStore.play) {
|
|
||||||
// 如果有保存的播放进度,应用它
|
|
||||||
if (playerStore.savedPlayProgress !== undefined) {
|
|
||||||
try {
|
|
||||||
// 设置音频位置
|
|
||||||
finalSound.seek(playerStore.savedPlayProgress);
|
|
||||||
// 同时更新时间显示
|
|
||||||
nowTime.value = playerStore.savedPlayProgress;
|
|
||||||
console.log('恢复播放进度:', playerStore.savedPlayProgress);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('恢复播放进度失败:', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
startProgressAnimation();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
console.warn('无法获取音频实例,跳过进度更新初始化');
|
console.warn('无法获取音频实例,跳过进度更新初始化');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -456,7 +456,7 @@ class AudioService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 播放控制相关
|
// 播放控制相关
|
||||||
play(url?: string, track?: SongResult, isPlay: boolean = true): Promise<Howl> {
|
play(url?: string, track?: SongResult, isPlay: boolean = true, seekTime: number = 0): Promise<Howl> {
|
||||||
// 每次调用play方法时,尝试强制重置锁(注意:仅在页面刷新后的第一次播放时应用)
|
// 每次调用play方法时,尝试强制重置锁(注意:仅在页面刷新后的第一次播放时应用)
|
||||||
if (!this.currentSound) {
|
if (!this.currentSound) {
|
||||||
console.log('首次播放请求,强制重置操作锁');
|
console.log('首次播放请求,强制重置操作锁');
|
||||||
@@ -599,6 +599,9 @@ class AudioService {
|
|||||||
// 音频加载成功后设置 EQ 和更新媒体会话
|
// 音频加载成功后设置 EQ 和更新媒体会话
|
||||||
if (this.currentSound) {
|
if (this.currentSound) {
|
||||||
try {
|
try {
|
||||||
|
if (seekTime > 0) {
|
||||||
|
this.currentSound.seek(seekTime);
|
||||||
|
}
|
||||||
console.log('audioService: 音频加载成功,设置 EQ');
|
console.log('audioService: 音频加载成功,设置 EQ');
|
||||||
await this.setupEQ(this.currentSound);
|
await this.setupEQ(this.currentSound);
|
||||||
this.updateMediaSessionMetadata(track);
|
this.updateMediaSessionMetadata(track);
|
||||||
|
|||||||
@@ -395,7 +395,6 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
const musicFull = ref(false);
|
const musicFull = ref(false);
|
||||||
const favoriteList = ref<Array<number | string>>(getLocalStorageItem('favoriteList', []));
|
const favoriteList = ref<Array<number | string>>(getLocalStorageItem('favoriteList', []));
|
||||||
const dislikeList = ref<Array<number | string>>(getLocalStorageItem('dislikeList', []));
|
const dislikeList = ref<Array<number | string>>(getLocalStorageItem('dislikeList', []));
|
||||||
const savedPlayProgress = ref<number | undefined>();
|
|
||||||
const showSleepTimer = ref(false); // 定时弹窗
|
const showSleepTimer = ref(false); // 定时弹窗
|
||||||
// 添加播放列表抽屉状态
|
// 添加播放列表抽屉状态
|
||||||
const playListDrawerVisible = ref(false);
|
const playListDrawerVisible = ref(false);
|
||||||
@@ -1080,7 +1079,6 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
const settingStore = useSettingsStore();
|
const settingStore = useSettingsStore();
|
||||||
const savedPlayList = getLocalStorageItem('playList', []);
|
const savedPlayList = getLocalStorageItem('playList', []);
|
||||||
const savedPlayMusic = getLocalStorageItem<SongResult | null>('currentPlayMusic', null);
|
const savedPlayMusic = getLocalStorageItem<SongResult | null>('currentPlayMusic', null);
|
||||||
const savedProgress = localStorage.getItem('playProgress');
|
|
||||||
|
|
||||||
if (savedPlayList.length > 0) {
|
if (savedPlayList.length > 0) {
|
||||||
setPlayList(savedPlayList);
|
setPlayList(savedPlayList);
|
||||||
@@ -1100,20 +1098,6 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await handlePlayMusic({ ...savedPlayMusic, isFirstPlay: true, playMusicUrl: undefined }, isPlaying);
|
await handlePlayMusic({ ...savedPlayMusic, isFirstPlay: true, playMusicUrl: undefined }, isPlaying);
|
||||||
|
|
||||||
if (savedProgress) {
|
|
||||||
try {
|
|
||||||
const progress = JSON.parse(savedProgress);
|
|
||||||
if (progress && progress.songId === savedPlayMusic.id) {
|
|
||||||
savedPlayProgress.value = progress.progress;
|
|
||||||
} else {
|
|
||||||
localStorage.removeItem('playProgress');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('解析保存的播放进度失败', e);
|
|
||||||
localStorage.removeItem('playProgress');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('重新获取音乐链接失败:', error);
|
console.error('重新获取音乐链接失败:', error);
|
||||||
play.value = false;
|
play.value = false;
|
||||||
@@ -1200,13 +1184,7 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
|
|
||||||
// 播放新音频,传递是否应该播放的状态
|
// 播放新音频,传递是否应该播放的状态
|
||||||
console.log('调用audioService.play,播放状态:', shouldPlay);
|
console.log('调用audioService.play,播放状态:', shouldPlay);
|
||||||
const newSound = await audioService.play(playMusicUrl.value, playMusic.value, shouldPlay);
|
const newSound = await audioService.play(playMusicUrl.value, playMusic.value, shouldPlay, initialPosition || 0);
|
||||||
|
|
||||||
// 如果有保存的进度,设置播放位置
|
|
||||||
if (initialPosition > 0) {
|
|
||||||
newSound.seek(initialPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发布音频就绪事件,让 MusicHook.ts 来处理设置监听器
|
// 发布音频就绪事件,让 MusicHook.ts 来处理设置监听器
|
||||||
window.dispatchEvent(new CustomEvent('audio-ready', { detail: { sound: newSound, shouldPlay } }));
|
window.dispatchEvent(new CustomEvent('audio-ready', { detail: { sound: newSound, shouldPlay } }));
|
||||||
|
|
||||||
@@ -1343,7 +1321,6 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
playListIndex,
|
playListIndex,
|
||||||
playMode,
|
playMode,
|
||||||
musicFull,
|
musicFull,
|
||||||
savedPlayProgress,
|
|
||||||
favoriteList,
|
favoriteList,
|
||||||
dislikeList,
|
dislikeList,
|
||||||
playListDrawerVisible,
|
playListDrawerVisible,
|
||||||
|
|||||||
+2
-2
@@ -22,8 +22,8 @@
|
|||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"types": [
|
"types": [
|
||||||
"naive-ui/volar",
|
"naive-ui/volar",
|
||||||
"./auto-imports.d.ts",
|
"./src/renderer/auto-imports.d.ts",
|
||||||
"./components.d.ts"
|
"./src/renderer/components.d.ts"
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["src/renderer/*"],
|
"@/*": ["src/renderer/*"],
|
||||||
|
|||||||
Reference in New Issue
Block a user