925ff669df
- 恢复 aplayer 初始变换为 translateY(0) 和 translateX(0),并自动触发点击以初始化状态 - 为 aplayer 列表项添加字体大小 12px 样式 - 简化 homeSide 组件模板格式,统一代码风格 - 移除节假日获取与显示相关逻辑(getHoli 函数及对应模板)
235 lines
6.1 KiB
Vue
235 lines
6.1 KiB
Vue
<template>
|
||
<!-- 准备一个容器用来存放音乐播放器 -->
|
||
<div ref="containerRef" id="aplayer"></div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
// https://aplayer.js.org/#/zh-Hans/
|
||
import APlayer from "aplayer"; // 引入音乐插件
|
||
import "aplayer/dist/APlayer.min.css"; // 引入音乐插件的样式
|
||
|
||
/** 歌单接口 */
|
||
interface MusicItem {
|
||
title: string;
|
||
author: string;
|
||
url: string;
|
||
pic: string;
|
||
lrc: string;
|
||
}
|
||
|
||
/** 歌单来源配置 */
|
||
const PLAYLIST_SERVER = "tencent";
|
||
const PLAYLIST_ID = "7681108669";
|
||
/** 歌单拉取接口(公共 meting API) */
|
||
const METING_API = `https://meting.mikus.ink/api?server=${PLAYLIST_SERVER}&type=playlist&id=${PLAYLIST_ID}`;
|
||
|
||
/** APlayer 基础配置 */
|
||
const APLAYER_INFO = {
|
||
fixed: true, // 开启吸底模式
|
||
loop: "all", // 音频循环播放, 可选值: 'all', 'one', 'none'
|
||
order: "list", // 音频循环顺序, 可选值: 'list', 'random'
|
||
preload: "metadata", // 预加载,可选值: 'none', 'metadata', 'auto'
|
||
volume: 0.5, // 默认音量,可选值: 0-1
|
||
mutex: true, // 互斥,默认为true,即不允许多个音频同时播放,当前播放的音频会暂停其他音频
|
||
lrcType: 3, // 歌词类型,可选值: 1, 2, 3
|
||
};
|
||
|
||
const containerRef = ref<HTMLDivElement | null>(null);
|
||
let ap: InstanceType<typeof APlayer> | null = null;
|
||
let miniswitcherEl: HTMLElement | null = null;
|
||
let listScrollEl: HTMLElement | null = null;
|
||
let onClickMiniswitcher: (() => void) | null = null;
|
||
let onListWheel: ((e: Event) => void) | null = null;
|
||
|
||
/** 拉取歌单并解析为 MusicItem[] */
|
||
async function fetchPlaylist(): Promise<MusicItem[]> {
|
||
const res = await fetch(METING_API);
|
||
if (!res.ok) throw new Error(`歌单请求失败: ${res.status}`);
|
||
const mlist: MusicItem[] = await res.json();
|
||
return mlist;
|
||
}
|
||
|
||
/** 初始化迷你切换按钮的显隐动画(自定义展开/收起效果) */
|
||
function setupMiniswitcher() {
|
||
miniswitcherEl = document.querySelector("#aplayer > div.aplayer-body > div.aplayer-miniswitcher > button");
|
||
const app = document.querySelector<HTMLElement>("#aplayer > div.aplayer-lrc");
|
||
const apic = document.querySelector<HTMLElement>("#aplayer div.aplayer-body");
|
||
if (!miniswitcherEl || !app || !apic) return;
|
||
|
||
let expanded = false;
|
||
app.style.display = "block";
|
||
app.style.transform = "translateY(0)";
|
||
apic.style.transform = "translateX(0)";
|
||
|
||
onClickMiniswitcher = () => {
|
||
if (expanded) {
|
||
app.style.transform = "translateY(50px)";
|
||
ap.lrc?.hide();
|
||
ap.list.hide();
|
||
apic.style.transform = "translateX(-66px)";
|
||
expanded = false;
|
||
} else {
|
||
app.style.transform = "translateY(0)";
|
||
ap.lrc?.show();
|
||
apic.style.transform = "translateX(0)";
|
||
expanded = true;
|
||
}
|
||
};
|
||
miniswitcherEl.addEventListener("click", onClickMiniswitcher);
|
||
miniswitcherEl?.click()
|
||
}
|
||
|
||
/** 阻止歌单列表滚轮事件冒泡,避免联动外层滚动 */
|
||
function setupListWheelGuard() {
|
||
listScrollEl = document.querySelector(".aplayer-list > ol");
|
||
if (!listScrollEl) return;
|
||
onListWheel = (e: Event) => e.stopPropagation();
|
||
listScrollEl.addEventListener("mousewheel", onListWheel);
|
||
}
|
||
|
||
/** 移动端(宽度 < 767)自动收起播放器为迷你模式 */
|
||
function applyMobileCollapse() {
|
||
const width = document.documentElement.clientWidth;
|
||
if (width < 767) miniswitcherEl?.click();
|
||
}
|
||
|
||
onMounted(async () => {
|
||
try {
|
||
const audio = await fetchPlaylist();
|
||
ap = new APlayer({
|
||
container: containerRef.value ?? undefined,
|
||
audio, // 音乐信息
|
||
...APLAYER_INFO, // 其他配置信息
|
||
});
|
||
|
||
setupMiniswitcher();
|
||
setupListWheelGuard();
|
||
applyMobileCollapse();
|
||
} catch (error) {
|
||
console.error("音乐列表加载失败:", error);
|
||
}
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
// 清理事件监听,避免内存泄漏
|
||
if (miniswitcherEl && onClickMiniswitcher) {
|
||
miniswitcherEl.removeEventListener("click", onClickMiniswitcher);
|
||
}
|
||
if (listScrollEl && onListWheel) {
|
||
listScrollEl.removeEventListener("mousewheel", onListWheel);
|
||
}
|
||
// 销毁 APlayer 实例
|
||
ap?.destroy();
|
||
ap = null;
|
||
});
|
||
</script>
|
||
<style lang="less" scoped>
|
||
#aplayer {
|
||
width: 480px; // 定个宽度
|
||
color: var(--n-text-color); // 设置颜色
|
||
background-color: #f5f5f8; // 设置背景色
|
||
|
||
:deep(.aplayer-body) {
|
||
color: var(--n-text-color); // 设置颜色
|
||
background-color: #f5f5f8; // 设置背景色
|
||
|
||
// bottom: 10px; // 设置底部距离
|
||
.aplayer-icon-lrc {
|
||
display: none;
|
||
}
|
||
}
|
||
|
||
:deep(.aplayer-icon):hover svg path {
|
||
fill: @blue; // 设置颜色
|
||
}
|
||
|
||
:deep(.aplayer-list) {
|
||
width: 418px !important;
|
||
background-color: #f5f5f8;
|
||
border: none;
|
||
}
|
||
|
||
:deep(.aplayer-list-light) {
|
||
font-size: 12px !important;
|
||
background: inherit;
|
||
color: @orange;
|
||
}
|
||
|
||
:deep(.aplayer-list) ol {
|
||
max-height: 160px !important;
|
||
|
||
// margin-bottom: 12px;
|
||
.aplayer-list-light .aplayer-list-author {
|
||
color: @orange !important;
|
||
}
|
||
}
|
||
|
||
:deep(.aplayer-list) ol li {
|
||
border: none;
|
||
}
|
||
|
||
:deep(.aplayer-list) ol li:hover {
|
||
background: inherit;
|
||
color: @blue;
|
||
border: none;
|
||
|
||
.aplayer-list-author {
|
||
color: @blue !important;
|
||
}
|
||
}
|
||
|
||
:deep(.aplayer-info) {
|
||
padding: 0 !important;
|
||
border-top: none;
|
||
}
|
||
|
||
:deep(.aplayer-icon) svg {
|
||
color: var(--n-text-color); // 设置颜色
|
||
background-color: var(--v-bgc); // 设置背景色
|
||
}
|
||
|
||
:deep(.aplayer-icon):hover svg {
|
||
color: @blue; // 设置颜色
|
||
}
|
||
|
||
:deep(.aplayer-miniswitcher) {
|
||
color: var(--n-text-color); // 设置颜色
|
||
background-color: var(--v-bgc); // 设置背景色
|
||
}
|
||
|
||
:deep(.aplayer-lrc) {
|
||
// text-align: right !important;
|
||
width: 368px !important;
|
||
transition: all 0.3s ease;
|
||
// left: unset;
|
||
bottom: 12px;
|
||
z-index: 999;
|
||
text-shadow: none !important;
|
||
}
|
||
|
||
:deep(.aplayer-lrc-contents) {
|
||
transition: all 0.5s ease !important;
|
||
|
||
p {
|
||
font-size: 12px !important;
|
||
color: @primary !important;
|
||
opacity: 0.8;
|
||
}
|
||
}
|
||
|
||
:deep(.aplayer-lrc-contents) {
|
||
.aplayer-lrc-current {
|
||
opacity: 1;
|
||
font-size: 13px !important;
|
||
color: @orange !important;
|
||
}
|
||
}
|
||
}
|
||
|
||
@media screen and (max-width: 800px) {
|
||
#aplayer {
|
||
width: 100vw !important;
|
||
}
|
||
}
|
||
</style>
|