Files
PC-official/static/js/song-request-station.js
DESKTOP-RQ919RC\Pc f1aa3a5d4a feat(ui): 优化标签样式并添加播放状态高亮效果
- 重构标签HTML结构,添加.tag-text容器
- 为当前播放标签添加旋转渐变边框效果
- 调整标签尺寸和内边距
- 添加控制台日志输出当前播放数据
- 更新VSCode端口配置
2025-10-11 18:54:11 +08:00

607 lines
25 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 = (tag, songs) => {
const randomIndex = Math.floor(Math.random() * songs.length);
const item = { ...songs[randomIndex], tag };
manageAudio(item);
};
// 管理音频播放
const manageAudio = (item) => {
const audio = audioPlayer.value;
closeAll();
setTimeout(() => {
if (audio?.src != item.path) audio.src = `./static/mp3/station/${item.path}`;
audio.play().then(() => (playData.value = { ...item, state: true }));
console.log("playData.value", playData.value);
}, 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);
};
// 切换下一首 或 上一首
const fastForward = (type = "fast") => {
if (!audioPlayer.value) return;
tags.value.forEach((item) => {
if (item.tag == playData.value.tag) {
const songs = item.songs || [];
const index = songs.findIndex((song) => song.name == playData.value.name);
// 通过下标 type = "fast" 为下一首 否则为上一首 并且需要轮回播放
const newIndex = type == "fast" ? (index + 1) % songs.length : (index - 1 + songs.length) % songs.length;
const data = { ...songs[newIndex], tag: item.tag };
manageAudio(data);
}
});
};
// 关闭所有播放
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");