382 lines
8.8 KiB
Vue
382 lines
8.8 KiB
Vue
<template>
|
||
<view class="floating-widget" v-if="show">
|
||
<!-- 悬浮按钮 -->
|
||
<view
|
||
class="floating-btn"
|
||
@click="togglePopup"
|
||
:style="{
|
||
backgroundColor: btnColor,
|
||
width: `${btnSize}px`,
|
||
height: `${btnSize}px`,
|
||
transform: `translateX(${currentOffsetX}px) translateY(${currentOffsetY}px)`,
|
||
}"
|
||
:class="{ active: isPopupVisible }"
|
||
@touchstart="touchstart"
|
||
@touchmove="touchmove"
|
||
@touchend="touchend"
|
||
>
|
||
<up-icon
|
||
name="plus"
|
||
size="24"
|
||
color="#ffffff"
|
||
:class="{ rotate: isPopupVisible }"
|
||
></up-icon>
|
||
</view>
|
||
|
||
<!-- 操作弹窗 -->
|
||
<view
|
||
class="popup"
|
||
v-if="isPopupVisible"
|
||
:class="{ show: isPopupVisible }"
|
||
:style="{
|
||
transform: `translateX(${currentOffsetX}px) translateY(${currentOffsetY}px)`,
|
||
}"
|
||
>
|
||
<view class="popup-arrow"></view>
|
||
<view class="popup-content">
|
||
<view
|
||
class="popup-item"
|
||
>
|
||
<text class="item-text">
|
||
<text> 当前环境:</text>
|
||
<text style="color: rgb(255, 0, 0);"> {{ uni.conf.debug ? "测试环境" : "生产环境" }}</text>
|
||
</text>
|
||
</view>
|
||
<view
|
||
class="popup-item"
|
||
v-for="(item, index) in operations"
|
||
:key="index"
|
||
@click="handleOperation(item.action)"
|
||
>
|
||
<text class="item-text">{{ item.name }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 点击外部关闭遮罩 -->
|
||
<view class="overlay" v-if="isPopupVisible" @click="closePopup"></view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, defineProps, defineEmits, computed } from "vue";
|
||
import { changeEnv } from "@/common/config.js";
|
||
import { Storelogin } from "@/stores/user.js";
|
||
const show = computed(() => {
|
||
// trial
|
||
const sysInfo = uni.getAccountInfoSync();
|
||
if (
|
||
sysInfo &&
|
||
sysInfo.miniProgram &&
|
||
sysInfo.miniProgram.envVersion == "develop"
|
||
) {
|
||
return true;
|
||
}
|
||
return false;
|
||
});
|
||
|
||
// 核心修改1:新增“历史累积偏移量”变量(保存上一次拖拽后的最终位置)
|
||
const lastOffsetX = ref(0); // 历史X偏移累积值
|
||
const lastOffsetY = ref(0); // 历史Y偏移累积值
|
||
const currentOffsetX = ref(0); // 当前拖拽的实时偏移(基于历史值)
|
||
const currentOffsetY = ref(0); // 当前拖拽的实时偏移(基于历史值)
|
||
|
||
const startX = ref(0);
|
||
const startY = ref(0);
|
||
|
||
// 核心修改2:touchstart - 基于历史偏移量初始化当前拖拽起点
|
||
function touchstart(e) {
|
||
const touch = e.touches[0];
|
||
startX.value = touch.clientX;
|
||
startY.value = touch.clientY;
|
||
// 关键:当前拖拽的起点 = 上一次拖拽的终点(历史累积偏移量)
|
||
currentOffsetX.value = lastOffsetX.value;
|
||
currentOffsetY.value = lastOffsetY.value;
|
||
}
|
||
|
||
// 核心修改3:touchmove - 实时计算“历史偏移量 + 当前拖拽距离”
|
||
function touchmove(e) {
|
||
const touch = e.touches[0];
|
||
// 当前拖拽的相对距离 = 现在触摸点 - 拖拽起点
|
||
const moveX = touch.clientX - startX.value;
|
||
const moveY = touch.clientY - startY.value;
|
||
// 实时偏移 = 历史累积偏移 + 当前相对移动距离(位置连续)
|
||
currentOffsetX.value = lastOffsetX.value + moveX;
|
||
currentOffsetY.value = lastOffsetY.value + moveY;
|
||
}
|
||
|
||
// 核心修改4:touchend - 保存当前拖拽终点为历史偏移量(供下次使用)
|
||
function touchend() {
|
||
// 关键:将本次拖拽的最终位置保存为历史值
|
||
lastOffsetX.value = currentOffsetX.value;
|
||
lastOffsetY.value = currentOffsetY.value;
|
||
}
|
||
|
||
// 定义组件属性
|
||
const props = defineProps({
|
||
// 操作选项列表
|
||
operations: {
|
||
type: Array,
|
||
default: () => [
|
||
{
|
||
name: "切换为测试环境",
|
||
icon: "arrowup",
|
||
action: "test",
|
||
color: "#007aff",
|
||
},
|
||
{
|
||
name: "切换为正式环境",
|
||
icon: "arrowup",
|
||
action: "prod",
|
||
color: "#007aff",
|
||
},
|
||
{
|
||
name: "复制token",
|
||
icon: "arrowup",
|
||
action: "token",
|
||
color: "#007aff",
|
||
},
|
||
{
|
||
name: "复制用户信息",
|
||
icon: "userInfo",
|
||
action: "userInfo",
|
||
color: "#52c41a",
|
||
},
|
||
{
|
||
name: "获取登录code",
|
||
icon: "userInfo",
|
||
action: "getLoginCode",
|
||
color: "#52c41a",
|
||
},
|
||
{
|
||
name: "复制当前门店信息",
|
||
icon: "userInfo",
|
||
action: "copyStoreInfo",
|
||
color: "#52c41a",
|
||
},
|
||
{
|
||
name: "复制当前门店用户信息",
|
||
icon: "userInfo",
|
||
action: "copyStoreUserInfo",
|
||
color: "#52c41a",
|
||
},
|
||
],
|
||
},
|
||
// 悬浮按钮颜色
|
||
btnColor: {
|
||
type: String,
|
||
default: "#007aff",
|
||
},
|
||
// 悬浮按钮大小(px)
|
||
btnSize: {
|
||
type: Number,
|
||
default: 60,
|
||
},
|
||
// 弹窗距离底部的距离
|
||
bottomDistance: {
|
||
type: Number,
|
||
default: 20,
|
||
},
|
||
// 弹窗距离右侧的距离
|
||
rightDistance: {
|
||
type: Number,
|
||
default: 20,
|
||
},
|
||
});
|
||
|
||
// 定义组件事件
|
||
const emit = defineEmits(["onOperation", "onOpen", "onClose"]);
|
||
|
||
// 弹窗显示状态
|
||
const isPopupVisible = ref(false);
|
||
|
||
// 切换弹窗显示/隐藏
|
||
const togglePopup = () => {
|
||
isPopupVisible.value = !isPopupVisible.value;
|
||
if (isPopupVisible.value) {
|
||
emit("onOpen");
|
||
} else {
|
||
emit("onClose");
|
||
}
|
||
};
|
||
|
||
// 关闭弹窗
|
||
const closePopup = () => {
|
||
if (isPopupVisible.value) {
|
||
isPopupVisible.value = false;
|
||
emit("onClose");
|
||
}
|
||
};
|
||
|
||
async function getWxloginCode() {
|
||
return new Promise((resolve, reject) => {
|
||
uni.login({
|
||
success: (res) => {
|
||
if (res.code) {
|
||
resolve(res.code);
|
||
} else {
|
||
console.log("获取登录凭证(code)失败!" + res.errMsg);
|
||
reject(res.errMsg);
|
||
}
|
||
},
|
||
});
|
||
});
|
||
}
|
||
const storelogin = Storelogin();
|
||
|
||
// 处理操作选择
|
||
const handleOperation = async (action) => {
|
||
let data = "";
|
||
|
||
if (action == "prod" || action == "test") {
|
||
changeEnv(action);
|
||
emit("onOperation", action);
|
||
uni.showToast({
|
||
title: "切换成功",
|
||
icon: "success",
|
||
});
|
||
uni.clearStorageSync();
|
||
|
||
await storelogin.actionslogin();
|
||
setTimeout(() => {
|
||
uni.reLaunch({
|
||
url: "/pages/index/index",
|
||
});
|
||
}, 1500);
|
||
|
||
closePopup();
|
||
return;
|
||
}
|
||
if (action == "token") {
|
||
data = uni.cache.get("token");
|
||
}
|
||
if (action == "userInfo") {
|
||
data = JSON.stringify(uni.cache.get("userInfo"));
|
||
}
|
||
if (action == "getLoginCode") {
|
||
data = await getWxloginCode();
|
||
}
|
||
if (action == "copyStoreInfo") {
|
||
data = JSON.stringify(uni.cache.get("shopInfo"));
|
||
}
|
||
if (action == "copyStoreUserInfo") {
|
||
data = JSON.stringify(uni.cache.get("shopUserInfo"));
|
||
}
|
||
console.log("data", data);
|
||
if (data) {
|
||
uni.setClipboardData({
|
||
data: data,
|
||
success: function () {},
|
||
});
|
||
}
|
||
|
||
emit("onOperation", action);
|
||
closePopup();
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.floating-widget {
|
||
position: fixed;
|
||
right: v-bind(rightDistance + "px");
|
||
bottom: v-bind(bottomDistance + "px");
|
||
z-index: 9999;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-end;
|
||
}
|
||
|
||
/* 悬浮按钮样式 */
|
||
.floating-btn {
|
||
border-radius: 50%;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
background: linear-gradient(98deg, #fe6d1100 40.64%, #ffd1b4 105.2%),
|
||
linear-gradient(259deg, #fe6d11 50.14%, #ffd1b4 114.93%);
|
||
box-shadow: 0 0.4375rem 0.95rem 0 #fe8b435e;
|
||
cursor: pointer;
|
||
/*transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); */
|
||
z-index: 1001;
|
||
}
|
||
|
||
.floating-btn.active {
|
||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
.floating-btn .rotate {
|
||
transform: rotate(45deg);
|
||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
|
||
/* 弹窗样式 */
|
||
.popup {
|
||
position: absolute;
|
||
bottom: calc(100% + 15px);
|
||
right: 0;
|
||
min-width: 180px;
|
||
background-color: #ffffff;
|
||
border-radius: 12px;
|
||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||
opacity: 0;
|
||
transform: translateY(10px) scale(0.95);
|
||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
z-index: 1000;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.popup.show {
|
||
opacity: 1;
|
||
transform: translateY(0) scale(1);
|
||
pointer-events: auto;
|
||
}
|
||
|
||
/* 弹窗箭头 */
|
||
.popup-arrow {
|
||
position: absolute;
|
||
right: 20px;
|
||
bottom: -8px;
|
||
width: 16px;
|
||
height: 16px;
|
||
background-color: #ffffff;
|
||
transform: rotate(45deg);
|
||
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.popup-content {
|
||
padding: 8px 0;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
/* 操作选项样式 */
|
||
.popup-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 12px 20px;
|
||
cursor: pointer;
|
||
transition: background-color 0.2s ease;
|
||
}
|
||
|
||
.popup-item:hover {
|
||
background-color: #f5f7fa;
|
||
}
|
||
|
||
.item-text {
|
||
font-size: 14px;
|
||
color: #333333;
|
||
line-height: 1;
|
||
}
|
||
|
||
/* 遮罩层样式 */
|
||
.overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(0, 0, 0, 0);
|
||
z-index: 999;
|
||
}
|
||
</style> |