feat: 小程序页面优化和新增剧本库功能
This commit is contained in:
@@ -1,8 +1,16 @@
|
||||
<template>
|
||||
<view class="music-player" :style="{ bottom: bottomPosition }">
|
||||
<view
|
||||
v-if="positionReady"
|
||||
class="music-player"
|
||||
:style="playerStyle"
|
||||
@touchstart.stop="handleTouchStart"
|
||||
@touchmove.stop.prevent="handleTouchMove"
|
||||
@touchend.stop="handleTouchEnd"
|
||||
@touchcancel.stop="handleTouchEnd"
|
||||
>
|
||||
<view
|
||||
class="music-toggle"
|
||||
:class="{ playing: isPlaying }"
|
||||
:class="{ playing: isPlaying, dragging: isDragging }"
|
||||
@click="toggleMusic"
|
||||
>
|
||||
<view class="music-disc" :class="{ spinning: isPlaying }"></view>
|
||||
@@ -12,14 +20,62 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { computed, ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
const isPlaying = ref(false)
|
||||
const bottomPosition = ref('180rpx')
|
||||
const positionReady = ref(false)
|
||||
const isDragging = ref(false)
|
||||
const playerPosition = ref({ x: 0, y: 0 })
|
||||
let audioInstance = null
|
||||
let windowMetrics = {
|
||||
width: 375,
|
||||
height: 667,
|
||||
statusBarHeight: 20,
|
||||
safeAreaBottom: 0,
|
||||
buttonSize: 44
|
||||
}
|
||||
let dragStart = null
|
||||
let suppressNextClick = false
|
||||
|
||||
// 背景音乐 URL - 使用原型中的音乐
|
||||
const MUSIC_URL = 'https://v3b.fal.media/files/b/0a8c9a0b/rStj8V-2tCe6bVYpCCcLN_output.mp3'
|
||||
const STORAGE_KEY = 'music_player_position'
|
||||
|
||||
const playerStyle = computed(() => ({
|
||||
left: `${playerPosition.value.x}px`,
|
||||
top: `${playerPosition.value.y}px`
|
||||
}))
|
||||
|
||||
const rpxToPx = (rpx, windowWidth = windowMetrics.width) => windowWidth * rpx / 750
|
||||
|
||||
const clamp = (value, min, max) => Math.max(min, Math.min(max, value))
|
||||
|
||||
const clampPosition = (position) => {
|
||||
const margin = 8
|
||||
const minY = Math.max(margin, windowMetrics.statusBarHeight + 8)
|
||||
const maxX = windowMetrics.width - windowMetrics.buttonSize - margin
|
||||
const maxY = windowMetrics.height - windowMetrics.safeAreaBottom - windowMetrics.buttonSize - margin
|
||||
return {
|
||||
x: clamp(Number(position?.x) || margin, margin, Math.max(margin, maxX)),
|
||||
y: clamp(Number(position?.y) || minY, minY, Math.max(minY, maxY))
|
||||
}
|
||||
}
|
||||
|
||||
const savePosition = () => {
|
||||
uni.setStorageSync(STORAGE_KEY, playerPosition.value)
|
||||
}
|
||||
|
||||
const restorePosition = () => {
|
||||
const saved = uni.getStorageSync(STORAGE_KEY)
|
||||
if (saved && typeof saved === 'object') {
|
||||
playerPosition.value = clampPosition(saved)
|
||||
return
|
||||
}
|
||||
playerPosition.value = clampPosition({
|
||||
x: windowMetrics.width - windowMetrics.buttonSize - rpxToPx(16),
|
||||
y: windowMetrics.height - windowMetrics.safeAreaBottom - windowMetrics.buttonSize - 96
|
||||
})
|
||||
}
|
||||
|
||||
const initAudio = () => {
|
||||
if (!audioInstance) {
|
||||
@@ -49,6 +105,7 @@ const initAudio = () => {
|
||||
}
|
||||
|
||||
const toggleMusic = async () => {
|
||||
if (suppressNextClick || isDragging.value) return
|
||||
initAudio()
|
||||
|
||||
if (isPlaying.value) {
|
||||
@@ -63,11 +120,58 @@ const toggleMusic = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleTouchStart = (event) => {
|
||||
const touch = event.touches?.[0]
|
||||
if (!touch) return
|
||||
dragStart = {
|
||||
x: touch.clientX,
|
||||
y: touch.clientY,
|
||||
originX: playerPosition.value.x,
|
||||
originY: playerPosition.value.y,
|
||||
moved: false
|
||||
}
|
||||
}
|
||||
|
||||
const handleTouchMove = (event) => {
|
||||
const touch = event.touches?.[0]
|
||||
if (!touch || !dragStart) return
|
||||
const deltaX = touch.clientX - dragStart.x
|
||||
const deltaY = touch.clientY - dragStart.y
|
||||
if (Math.abs(deltaX) > 3 || Math.abs(deltaY) > 3) {
|
||||
dragStart.moved = true
|
||||
isDragging.value = true
|
||||
}
|
||||
if (!dragStart.moved) return
|
||||
playerPosition.value = clampPosition({
|
||||
x: dragStart.originX + deltaX,
|
||||
y: dragStart.originY + deltaY
|
||||
})
|
||||
}
|
||||
|
||||
const handleTouchEnd = () => {
|
||||
if (dragStart?.moved) {
|
||||
playerPosition.value = clampPosition(playerPosition.value)
|
||||
savePosition()
|
||||
suppressNextClick = true
|
||||
setTimeout(() => {
|
||||
suppressNextClick = false
|
||||
}, 180)
|
||||
}
|
||||
dragStart = null
|
||||
isDragging.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 使用新的推荐 API 替代已弃用的 getSystemInfoSync
|
||||
const windowInfo = uni.getWindowInfo()
|
||||
const safeAreaBottom = windowInfo.safeAreaInsets?.bottom || 0
|
||||
bottomPosition.value = `${safeAreaBottom + 96}px`
|
||||
const windowInfo = uni.getWindowInfo ? uni.getWindowInfo() : uni.getSystemInfoSync()
|
||||
windowMetrics = {
|
||||
width: windowInfo.windowWidth || 375,
|
||||
height: windowInfo.windowHeight || 667,
|
||||
statusBarHeight: windowInfo.statusBarHeight || 20,
|
||||
safeAreaBottom: windowInfo.safeAreaInsets?.bottom || 0,
|
||||
buttonSize: rpxToPx(88, windowInfo.windowWidth || 375)
|
||||
}
|
||||
restorePosition()
|
||||
positionReady.value = true
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
@@ -86,8 +190,9 @@ defineExpose({
|
||||
<style scoped>
|
||||
.music-player {
|
||||
position: fixed;
|
||||
right: 16rpx;
|
||||
z-index: 1000;
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
}
|
||||
|
||||
.music-toggle {
|
||||
@@ -106,6 +211,13 @@ defineExpose({
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.music-toggle.dragging {
|
||||
transform: scale(1.06);
|
||||
opacity: 0.78;
|
||||
border-color: rgba(216, 180, 254, 0.42);
|
||||
box-shadow: 0 0 34rpx rgba(168, 85, 247, 0.32);
|
||||
}
|
||||
|
||||
.music-toggle:active {
|
||||
transform: scale(0.95);
|
||||
opacity: 0.6;
|
||||
|
||||
Reference in New Issue
Block a user