Files
happy-life-star/mini-program/src/components/MusicPlayer.vue
T
peanut 755059807a feat: 优化管理后台页面UI、修复TS编译错误、新增人生事件模块
- 优化 AI 配置列表页面:重构统计卡片、搜索表单、表格列展示
- 修复 3 处 TypeScript TS6133 编译错误,恢复构建
- 新增管理员修改密码和重置密码功能
- 优化小程序多个页面样式和交互
- 人生事件模块完善

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 23:23:09 +08:00

167 lines
3.3 KiB
Vue

<template>
<view class="music-player" :style="{ bottom: bottomPosition }">
<view
class="music-toggle"
:class="{ playing: isPlaying }"
@click="toggleMusic"
>
<view class="music-disc" :class="{ spinning: isPlaying }"></view>
<view class="music-note"></view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const isPlaying = ref(false)
const bottomPosition = ref('180rpx')
let audioInstance = null
// 背景音乐 URL - 使用原型中的音乐
const MUSIC_URL = 'https://v3b.fal.media/files/b/0a8c9a0b/rStj8V-2tCe6bVYpCCcLN_output.mp3'
const initAudio = () => {
if (!audioInstance) {
audioInstance = uni.createInnerAudioContext()
audioInstance.src = MUSIC_URL
audioInstance.loop = true
audioInstance.autoplay = false
audioInstance.onPlay(() => {
isPlaying.value = true
})
audioInstance.onPause(() => {
isPlaying.value = false
})
audioInstance.onError((err) => {
console.error('音乐播放失败:', err)
isPlaying.value = false
uni.showToast({ title: '音乐资源暂不可用', icon: 'none' })
})
audioInstance.onEnded(() => {
isPlaying.value = false
})
}
}
const toggleMusic = async () => {
initAudio()
if (isPlaying.value) {
audioInstance.pause()
} else {
try {
audioInstance.play()
} catch (err) {
console.error('音乐播放失败:', err)
isPlaying.value = false
}
}
}
onMounted(() => {
// 使用新的推荐 API 替代已弃用的 getSystemInfoSync
const windowInfo = uni.getWindowInfo()
const safeAreaBottom = windowInfo.safeAreaInsets?.bottom || 0
bottomPosition.value = `${safeAreaBottom + 96}px`
})
onUnmounted(() => {
if (audioInstance) {
audioInstance.stop()
audioInstance.destroy()
}
})
defineExpose({
toggleMusic,
isPlaying
})
</script>
<style scoped>
.music-player {
position: fixed;
right: 16rpx;
z-index: 1000;
}
.music-toggle {
width: 88rpx;
height: 88rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.2);
transition: all 0.3s ease;
opacity: 0.4;
}
.music-toggle:active {
transform: scale(0.95);
opacity: 0.6;
}
.music-toggle.playing {
opacity: 1;
border-color: rgba(168, 85, 247, 0.5);
box-shadow: 0 0 30rpx rgba(168, 85, 247, 0.3);
}
.music-disc {
position: absolute;
inset: 0;
border-radius: 50%;
border: 1px solid rgba(168, 85, 247, 0.2);
transition: all 0.3s ease;
}
.music-disc.spinning {
animation: spin 3s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.music-note {
position: relative;
width: 26rpx;
height: 38rpx;
z-index: 1;
}
.music-note::before {
content: '';
position: absolute;
left: 2rpx;
bottom: 0;
width: 18rpx;
height: 14rpx;
border: 5rpx solid rgba(196, 181, 253, 0.86);
border-radius: 50%;
}
.music-note::after {
content: '';
position: absolute;
right: 0;
top: 0;
width: 9rpx;
height: 34rpx;
border-radius: 6rpx;
background: rgba(196, 181, 253, 0.86);
box-shadow: 8rpx 3rpx 0 rgba(196, 181, 253, 0.42);
}
</style>