Files

236 lines
4.8 KiB
Vue
Raw Permalink Normal View History

2026-06-02 16:23:56 +08:00
<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>