const { createApp, ref, onMounted, nextTick, onUnmounted, computed } = Vue; createApp({ setup() { // 标签数据 let tags = ref([]); let tagsFill = ref([]); // 获取容器 const containerFill = ref(null); const container = ref(null); // 计算容器尺寸 const getContainerDimensions = () => { const containerWidth = container.value.offsetWidth; const containerHeight = container.value.offsetHeight; return { containerWidth, containerHeight }; }; let bubbleContainer = null; let bubbleContainerFill = null; // 初始创建标签 const init = () => { let tagsData = [ { name: "孤独角落的守望", path: "孤独角落的守望.MP3", tags: ["浪漫", "爱情", "悲伤的角落", "复古旋律", "流行"], }, { name: "碎梦残爱", path: "碎梦残爱.MP3", tags: ["浪漫", "爱情", "悲伤的角落", "复古旋律", "流行"], }, { name: "心碎的声音", path: "心碎的声音.MP3", tags: ["浪漫", "爱情", "悲伤的角落", "复古旋律", "流行"], }, { name: "夜尽破晓", path: "夜尽破晓.MP3", tags: ["浪漫", "爱情", "悲伤的角落", "梦幻", "自由鼓点", "赛博心跳", "自由摇摆", "微风轻轻吹"], }, { name: "秋江别", path: "秋江别.mp3", tags: ["浪漫", "爱情", "相思", "古风", "江湖", "悲伤的角落", "温柔女声"], }, { name: "锁心劫", path: "锁心劫.mp3", tags: ["浪漫", "爱情", "相思", "古风", "江湖", "悲伤的角落", "温柔女声"], }, { name: "大提琴与钢琴", path: "《大提琴与钢琴》纯音乐.m4a", tags: ["浪漫", "温柔", "幻想曲", "梦幻", "纯音乐", "自由摇摆", "微风轻轻吹"], }, { name: "经典游戏怀旧关卡", path: "《经典游戏怀旧关卡》纯音乐.mp3", tags: ["幻想曲", "梦幻", "纯音乐", "节奏大师", "快乐节拍", "自由鼓点", "赛博心跳", "赛博空间"], }, { name: "灵动琴音点亮旅行Vlog之旅", path: "《灵动琴音点亮旅行Vlog之旅》纯音乐.mp3", tags: ["彩虹泡泡", "幻想曲", "梦幻", "纯音乐", "节奏大师", "快乐节拍", "自由鼓点", "赛博心跳", "赛博空间"], }, { name: "品牌创新科技", path: "《品牌创新科技》纯音乐.mp3", tags: ["纯音乐", "节奏大师", "快乐节拍", "自由鼓点", "赛博心跳", "赛博空间"], }, { name: "琴音交织的青春动画恋曲", path: "《琴音交织的青春动画恋曲》纯音乐.mp3", tags: ["彩虹泡泡", "浪漫", "温柔", "幻想曲", "梦幻", "纯音乐", "自由摇摆", "微风轻轻吹", "阳光正好", "青草香"], }, { name: "我的金属心跳", path: "《我的金属心跳》.mp3", tags: ["流行", "自由摇摆", "赛博心跳", "节奏大师", "摇滚"], }, { name: "向前跑", path: "《向前跑》.mp3", tags: ["流行", "自由摇摆"], }, { name: "战斗氛围", path: "《战斗氛围》纯音乐.m4a", tags: ["纯音乐", "节奏大师", "快乐节拍", "自由鼓点", "赛博心跳", "赛博空间", "摇滚"], }, { name: "长安三万里", path: "《长安三万里》纯音乐.mp3", tags: ["彩虹泡泡", "浪漫", "温柔", "幻想曲", "梦幻", "纯音乐", "星际漫游", "自由摇摆", "微风轻轻吹", "古风", "青草香", "阳光正好"], }, { name: "助眠", path: "《助眠》纯音乐.mp3", tags: ["彩虹泡泡", "浪漫", "温柔", "幻想曲", "梦幻", "纯音乐", "星际漫游"], }, { name: "Compass Heart(中文版)", path: "Compass Heart(中文版).mp3", tags: ["浪漫", "爱情", "相思", "流行", "悲伤的角落", "温柔女声", "自由鼓点"], }, { name: "I Remember", path: "I Remember.mp3", tags: ["彩虹泡泡", "浪漫", "温柔", "幻想曲", "梦幻", "自由摇摆", "微风轻轻吹", "温柔女声", "青草香", "阳光正好"], }, { name: "炒股的人不听慢歌", path: "炒股的人不听慢歌.MP3", tags: ["节奏大师", "快乐节拍", "自由鼓点", "赛博心跳", "赛博空间", "摇滚"], }, { name: "成都真香故事", path: "成都真香故事.mp3", tags: ["生日祝福"], }, { name: "大闹天宫", path: "大闹天宫.mp3", tags: ["自由摇摆", "解压宣泄", "影视配乐", "在路上"], }, { name: "都市周末狂欢夜", path: "都市周末狂欢夜.mp3", tags: ["阳光正好", "自由摇摆", "摇滚", "解压宣泄", "派对聚会"], }, { name: "光耀华夏", path: "光耀华夏.mp3", tags: ["江湖", "影视配乐", "相思", "在路上", "流行金曲"], }, { name: "广州", path: "广州.mp3", tags: ["浪漫", "在路上", "甜蜜时光"], }, { name: "蝴蝶与坦克", path: "蝴蝶与坦克.mp3", tags: ["星际漫游", "幻想曲", "赛博心跳", "赛博空间", "节奏大师", "快乐节拍", "自由鼓点"], }, { name: "加速心跳", path: "加速心跳.mp3", tags: ["幻想曲", "运动健身", "节奏大师", "快乐节拍"], }, { name: "脚步写下自由", path: "脚步写下自由.mp3", tags: ["开心到飞起", "微风轻轻吹", "解压宣泄", "通勤路上"], }, { name: "经纬线", path: "经纬线.mp3", tags: ["温柔", "温柔女声", "民谣", "悲伤的角落", "助眠放松"], }, { name: "旧唱片", path: "旧唱片.mp3", tags: ["爱情", "相思", "约会浪漫", "悲伤的角落"], }, { name: "快乐广场舞", path: "快乐广场舞.mp3", tags: ["复古旋律", "节奏大师", "快乐节拍"], }, { name: "梅雨季", path: "梅雨季.mp3", tags: ["浪漫", "爱情", "悲伤的角落", "民谣", "微风轻轻吹", "去旅行", "在路上", "学习BGM"], }, { name: "人生的过客", path: "人生的过客.mp3", tags: ["民谣", "旅行路上", "微风轻轻吹", "在路上", "专注工作/学习"], }, { name: "殇", path: "殇.mp3", tags: ["复古旋律", "相思", "江湖"], }, { name: "深夜咖啡馆", path: "深夜咖啡馆.mp3", tags: ["浪漫", "爱情", "相思", "悲伤的角落", "助眠放松", "通勤路上"], }, { name: "世界在转动", path: "世界在转动.mp3", tags: ["温柔", "阳光正好", "解压宣泄", "甜蜜时光"], }, { name: "她说", path: "她说.mp3", tags: ["悲伤的角落", "温柔女声", "青草香"], }, { name: "天平行者", path: "天平行者 .mp3", tags: ["生日祝福"], }, { name: "跳楼机-上班版", path: "跳楼机-上班版.MP3", tags: ["专注工作/学习", "通勤路上", "解压宣泄"], }, { name: "童年时光机", path: "童年时光机.mp3", tags: ["快乐节拍", "生日祝福", "绘本音乐"], }, { name: "无题", path: "无题.mp3", tags: ["节奏大师", "自由鼓点", "幻想曲", "纯音乐"], }, { name: "五星闪耀(青春)", path: "五星闪耀(青春).mp3", tags: ["专注工作/学习", "快乐节拍", "学习BGM"], }, { name: "吸烟区", path: "吸烟区.mp3", tags: ["通勤路上", "运动健身", "自由摇摆"], }, { name: "下一站旅行", path: "下一站旅行.MP3", tags: ["旅行路上", "微风轻轻吹"], }, { name: "尊重·成长·共赢", path: "尊重·成长·共赢.mp3", tags: ["派对聚会", "节奏大师"], }, { name: "Phantom in the Code", path: "Phantom in the Code.mp3", tags: ["彩虹泡泡", "温柔女声", "影视配乐"], }, { name: "We are family", path: "We are family.MP3", tags: ["通勤路上", "在路上", "派对聚会"], }, { name: "班味退散", path: "班味退散.MP3", tags: ["解压宣泄", "通勤路上", "开心到飞起", "自由摇摆"], }, ]; // 提取 tags 数组 // 执行转换 tagsData = transformMusicData(tagsData); console.log(tagsData); const redCount = Math.min(5, tagsData.length); const redIndexes = []; while (redIndexes.length < redCount) { const randomIdx = Math.floor(Math.random() * tagsData.length); if (!redIndexes.includes(randomIdx)) redIndexes.push(randomIdx); } tagsData.forEach((item, index) => { item["type"] = getRandomOutcome(); if (redIndexes.includes(index)) item["isred"] = 1; }); tags.value = tagsData; const fillLength = tags.value.length * 1.5; let tagAll = []; for (let i = 0; i < fillLength; i++) { tagAll.push({ type: Math.floor(Math.random() * 5) + 1, }); } tagsFill.value = tagAll; bubbleContainer?.[0]?.destroy(); bubbleContainerFill?.[0]?.destroy(); nextTick(() => { const { containerWidth, containerHeight } = getContainerDimensions(); bubbleContainerFill = tagCloud({ selector: "#bubbleContainerFill", // 元素选择器,id 或 class radius: [containerWidth / 2, containerHeight / 2], // 滚动横/纵轴半径, 默认60,单位px,取值60,[60],[60, 60] mspeed: "normal", // 滚动最大速度, 取值: slow, normal(默认), fast ispeed: "slow", // 滚动初速度, 取值: slow, normal(默认), fast direction: 45, // 初始滚动方向, 取值角度(顺时针360): 0对应top, 90对应left, 135对应right-bottom(默认)... keep: false, // 鼠标移出组件后是否继续随鼠标滚动, 取值: false, true(默认) 对应 减速至初速度滚动, 随鼠标滚动 multicolour: false, // 彩色字体,颜色随机,取值:true(默认),false }); bubbleContainer = tagCloud({ selector: "#bubbleContainer", // 元素选择器,id 或 class radius: [containerWidth / 2, containerHeight / 2], mspeed: "normal", ispeed: "normal", direction: 135, keep: false, multicolour: false, }); }); }; const transformMusicData = (data) => { // 使用Map存储标签和对应的歌曲 const tagMap = new Map(); // 遍历每首歌 data.forEach((song) => { // 遍历当前歌曲的所有标签 song.tags.forEach((tag) => { // 准备歌曲信息(仅包含name和path) const songInfo = { name: song.name, path: song.path, }; // 如果标签已存在于Map中,添加歌曲;否则创建新条目 if (tagMap.has(tag)) { tagMap.get(tag).push(songInfo); } else { tagMap.set(tag, [songInfo]); } }); }); // 将Map转换为目标格式的数组 return Array.from(tagMap).map(([tag, songs]) => ({ tag, songs, })); }; // 生成单次随机结果的函数 const getRandomOutcome = () => { const random = Math.random(); // 生成0-1之间的随机数 let cumulativeProbability = 0; const outcomes = [0.1, 0.2, 0.3, 0.4]; for (const outcome of outcomes) { cumulativeProbability += outcome; if (random < cumulativeProbability) { return outcomes.indexOf(outcome) + 1; } } }; onMounted(() => { init(); window.addEventListener("resize", () => { init(); }); // 添加进度更新事件监听器 if (audioPlayer.value) { volume.value = audioPlayer.value.volume * 100; audioPlayer.value.addEventListener("timeupdate", getProgress); audioPlayer.value.addEventListener("loadedmetadata", getProgress); } }); // 组件卸载时清理事件监听器 onUnmounted(() => { audioPlayer?.value?.removeEventListener("timeupdate", getProgress); audioPlayer?.value?.removeEventListener("loadedmetadata", getProgress); }); const handleMouseleave = (e) => { e.target.style.animationPlayState = "running"; }; const handleMouseenter = (e) => { e.target.style.animationPlayState = "paused"; }; const audioPlayer = ref(null); const clickSongs = (songs) => { const randomIndex = Math.floor(Math.random() * songs.length); const item = songs[randomIndex]; manageAudio(item); }; // 管理音频播放 const manageAudio = (item) => { const audio = audioPlayer.value; closeAll(); setTimeout(() => { console.log("item", item); if (audio?.src != item.path) audio.src = `./static/mp3/station/${item.path}`; // audio.src = "https://app.gter.net/image/miniApp/mp3/1.mp3"; // audio.src = "/static/mp3/1.MP3"; audio.play().then(() => (playData.value = { ...item, state: true })); }, 500); }; // 重新播放 const rePlay = () => { if (!playData.value) return; const item = playData.value; manageAudio(item); }; // 播放 组件数据 let playData = ref(null); // 响应式数据:音量值、是否静音 let volume = ref(100); // 计算并设置音量百分比 const setVolumePercentage = (percentage) => { const volumePercent = Math.max(0, Math.min(100, percentage)); volume.value = Math.abs(~~volumePercent); // 设置音频元素的音量(范围是0-1) if (audioPlayer.value) audioPlayer.value.volume = volume.value / 100; }; // 处理音量进度条点击 const handleVolumeClick = (event) => { // 获取进度条元素 const progressBar = event.currentTarget; const rect = progressBar.getBoundingClientRect(); const clickPosition = rect.bottom - event.clientY; const percentage = (clickPosition / rect.height) * 100; setVolumePercentage(percentage); }; let volumeShow = ref(false); // 处理音量进度条拖拽 let isDragging = false; const startDrag = (event) => { isDragging = true; handleVolumeDrag(event); // 添加事件监听器 document.addEventListener("mousemove", handleVolumeDrag); document.addEventListener("mouseup", stopDrag); }; const handleVolumeDrag = (event) => { if (!isDragging) return; // 获取音量进度条元素 const progressBar = document.querySelector(".sound-control .progress"); if (!progressBar) return; const rect = progressBar.getBoundingClientRect(); // 计算拖拽位置相对于进度条的比例 let dragPosition = rect.bottom - event.clientY; // 限制在进度条范围内 dragPosition = Math.max(0, Math.min(dragPosition, rect.height)); const percentage = (dragPosition / rect.height) * 100; setVolumePercentage(percentage); }; const stopDrag = () => { isDragging = false; document.removeEventListener("mousemove", handleVolumeDrag); document.removeEventListener("mouseup", stopDrag); }; const handleVolumeShow = () => (volumeShow.value = true); const handleVolumeHide = () => (volumeShow.value = false); onUnmounted(() => { // 确保移除所有拖拽相关事件 document.removeEventListener("mousemove", handleVolumeDrag); document.removeEventListener("mouseup", stopDrag); document.removeEventListener("mousemove", handleBarDragBottomDrag); document.removeEventListener("mouseup", stopBarDragBottom); }); const progress = ref(0); // 播放进度百分比 // 响应式变量存储当前播放时间和总时长 const currentTimeFormatted = ref("00:00"); const durationFormatted = ref("00:00"); // 格式化时间函数:将秒数转换为 MM:SS 格式 const formatTime = (seconds) => { const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`; }; // 更新进度的函数 const getProgress = () => { if (!audioPlayer.value) return; const currentTime = audioPlayer.value.currentTime || 0; const duration = audioPlayer.value.duration || 0; const p = duration > 0 ? (currentTime / duration) * 100 : 0; progress.value = p; // 更新时间显示 currentTimeFormatted.value = formatTime(currentTime); durationFormatted.value = formatTime(duration); }; // 快进 和 后退 10秒 const fastForward = (type = "fast") => { if (!audioPlayer.value) return; console.log("playData.value", playData.value); const src = playData.value?.path || ""; if (audioPlayer.value.src != src) { manageAudio(playData.value); return; } let currentTime = audioPlayer.value.currentTime || 0; const duration = audioPlayer.value.duration || 0; let newTime = 0; if (type == "fast") newTime = Math.min(currentTime + 10, duration); else newTime = Math.max(currentTime - 10, 0); audioPlayer.value.currentTime = newTime; getProgress(); }; // 关闭所有播放 const closeAll = () => { audioPlayer.value.pause(); playData.value && (playData.value.state = false); }; // 处理音量进度条拖拽 let isBarBottomDragging = false; const startBarDragBottom = (event) => { isBarBottomDragging = true; handleBarDragBottomDrag(event); // 添加事件监听器 document.addEventListener("mousemove", handleBarDragBottomDrag); document.addEventListener("mouseup", stopBarDragBottom); }; const stopBarDragBottom = () => { isBarBottomDragging = false; document.removeEventListener("mousemove", handleBarDragBottomDrag); document.removeEventListener("mouseup", stopBarDragBottom); }; // 直接点击进度条 跳转 const handleBarDragBottomClick = (event) => { // 获取进度条元素 const progressBar = event.currentTarget; const rect = progressBar.getBoundingClientRect(); const clickPosition = event.clientX - rect.left; const percentage = clickPosition / rect.width; // 限制百分比在0-100之间 const clampedPercentage = Math.max(0, Math.min(100, percentage)); updatePlay(clampedPercentage); }; const handleBarDragBottomDrag = (event) => { // console.log("event", event); if (!isBarBottomDragging) return; // 获取音量进度条元素 const progressBar = document.querySelector(".bottom-play .bottom-middle .progress-bar"); if (!progressBar) return; const rect = progressBar.getBoundingClientRect(); // 计算拖拽位置相对于进度条的比例 let dragPosition = event.clientX - rect.left; // 限制在进度条范围内 dragPosition = Math.max(0, Math.min(dragPosition, rect.width)); const percentage = dragPosition / rect.width; updatePlay(percentage); }; const updatePlay = (percentage) => { if (!audioPlayer.value) return; const duration = audioPlayer.value.duration || 0; let newTime = duration * percentage; newTime = Math.max(0, Math.min(duration, newTime)); audioPlayer.value.currentTime = newTime; getProgress(); }; return { startBarDragBottom, handleBarDragBottomClick, closeAll, fastForward, progress, durationFormatted, currentTimeFormatted, rePlay, handleVolumeHide, handleVolumeShow, stopDrag, handleVolumeDrag, startDrag, volumeShow, handleVolumeClick, setVolumePercentage, volume, audioPlayer, playData, handleMouseleave, handleMouseenter, clickSongs, tags, tagsFill, containerFill, container }; }, }).mount("#song-request-station");