236 lines
4.8 KiB
Vue
236 lines
4.8 KiB
Vue
<template>
|
||
<!-- 唯一根节点 → 修复报错 -->
|
||
<view class="float-guide-wrapper">
|
||
<!-- 纯JS丝滑悬浮球 -->
|
||
<view
|
||
class="float-ball"
|
||
:style="{
|
||
left: x + 'px',
|
||
top: y + 'px',
|
||
transition: isAnimating ? 'all 0.2s ease-out' : 'none'
|
||
}"
|
||
@touchstart="onStart"
|
||
@touchmove.stop.prevent="onMove"
|
||
@touchend="onEnd"
|
||
@click="handleBallClick"
|
||
>
|
||
<!-- 展开状态:箭头在左,文字在右 -->
|
||
<view class="ball-content" v-if="!isFolded">
|
||
<view class="fold-btn" @click.stop="fold">
|
||
<text class="fold-icon">›</text>
|
||
</view>
|
||
<view class="ball-main">
|
||
<text class="text">使用教程</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 收起状态 -->
|
||
<view class="ball-folded" v-else>
|
||
<text class="text">展开</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 视频弹窗 -->
|
||
<view class="video-mask" v-if="showVideoPlayer" @click="closeVideo">
|
||
<view class="video-wrapper" @click.stop>
|
||
<view class="close-btn" @click="closeVideo">×</view>
|
||
<video :src="videoSrc" class="guide-video" controls loop show-progress="false"
|
||
object-fit="contain"></video>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
name: "floatGuide",
|
||
props: {
|
||
videoSrc: {
|
||
type: String,
|
||
default: "/static/guide.mp4"
|
||
},
|
||
initX: {
|
||
type: Number,
|
||
default: null
|
||
},
|
||
initY: {
|
||
type: Number,
|
||
default: null
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
x: 0,
|
||
y: 0,
|
||
isFolded: false,
|
||
showVideoPlayer: false,
|
||
isAnimating: false,
|
||
startX: 0,
|
||
startY: 0,
|
||
pageWidth: 0,
|
||
pageHeight: 0
|
||
};
|
||
},
|
||
mounted() {
|
||
this.initPosition();
|
||
},
|
||
methods: {
|
||
initPosition() {
|
||
uni.getSystemInfo({
|
||
success: (res) => {
|
||
this.pageWidth = res.windowWidth;
|
||
this.pageHeight = res.windowHeight;
|
||
if (this.initX !== null && this.initY !== null) {
|
||
this.x = this.initX;
|
||
this.y = this.initY;
|
||
} else {
|
||
this.x = this.pageWidth - 160;
|
||
this.y = this.pageHeight - 260;
|
||
}
|
||
}
|
||
});
|
||
},
|
||
onStart(e) {
|
||
const tx = e.touches[0].clientX;
|
||
const ty = e.touches[0].clientY;
|
||
this.startX = tx - this.x;
|
||
this.startY = ty - this.y;
|
||
this.isAnimating = false;
|
||
},
|
||
onMove(e) {
|
||
const tx = e.touches[0].clientX;
|
||
const ty = e.touches[0].clientY;
|
||
let newX = tx - this.startX;
|
||
let newY = ty - this.startY;
|
||
|
||
newX = Math.max(10, Math.min(newX, this.pageWidth - 80));
|
||
newY = Math.max(40, Math.min(newY, this.pageHeight - 100));
|
||
|
||
this.x = newX;
|
||
this.y = newY;
|
||
},
|
||
onEnd() {
|
||
this.isAnimating = true;
|
||
},
|
||
handleBallClick() {
|
||
if (this.isFolded) {
|
||
this.isFolded = false;
|
||
} else {
|
||
this.showVideoPlayer = true;
|
||
}
|
||
},
|
||
fold() {
|
||
this.isFolded = true;
|
||
},
|
||
closeVideo() {
|
||
this.showVideoPlayer = false;
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.float-guide-wrapper {
|
||
display: block;
|
||
}
|
||
|
||
.float-ball {
|
||
position: fixed;
|
||
z-index: 99999;
|
||
user-select: none;
|
||
}
|
||
|
||
/* 展开样式:箭头左,文字右,正常顺序 */
|
||
.ball-content {
|
||
display: flex;
|
||
align-items: center;
|
||
background: linear-gradient(90deg, #ff2f31 0%, #ff9379 100%);
|
||
border-radius: 40rpx;
|
||
height: 80rpx;
|
||
padding: 0 20rpx;
|
||
box-shadow: 0 6rpx 20rpx rgba(255, 60, 60, 0.2);
|
||
}
|
||
|
||
.ball-main {
|
||
padding: 0 10rpx;
|
||
}
|
||
|
||
.ball-content .text {
|
||
color: #fff;
|
||
font-size: 26rpx;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.fold-btn {
|
||
width: 44rpx;
|
||
height: 44rpx;
|
||
border-radius: 50%;
|
||
background: rgba(255, 255, 255, 0.2);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.fold-icon {
|
||
color: #fff;
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.ball-folded {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
border-radius: 50%;
|
||
background: linear-gradient(90deg, #ff2f31 0%, #ff9379 100%);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow: 0 6rpx 20rpx rgba(255, 60, 60, 0.2);
|
||
}
|
||
|
||
.ball-folded .text {
|
||
color: #fff;
|
||
font-size: 24rpx;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.video-mask {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.7);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 999999;
|
||
}
|
||
|
||
.video-wrapper {
|
||
position: relative;
|
||
width: 90%;
|
||
}
|
||
|
||
.guide-video {
|
||
width: 100%;
|
||
height: 420rpx; /* 必须加高度! */
|
||
border-radius: 20rpx;
|
||
background-color: #000; /* 防止加载瞬间闪白 */
|
||
}
|
||
|
||
.close-btn {
|
||
position: absolute;
|
||
top: -88rpx;
|
||
right: 0;
|
||
width: 64rpx;
|
||
height: 64rpx;
|
||
background: rgba(255, 255, 255, 0.2);
|
||
color: #fff;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 36rpx;
|
||
}
|
||
</style> |