uniapp实现全局悬浮按钮

2,063 阅读2分钟

直接上组件,可拖动

image.png

1.在components新建suspend组件

<template>
  <view>
    <movable-area class="movable-area">
      <movable-view class="movable-view" :x="x" :y="y" direction="all">
        <view class="newProblem" @click="xxx">
          <image src="https://xxx/minapp/static/icon.png"></image>
          <text>反馈</text>
        </view>
      </movable-view>
    </movable-area>
  </view>
</template>

<script>
import { mapState } from "vuex";
export default {
  data() {
    return {
      x: 1000, // x大于默认的值就会在最右边
      y: 1000
    };
  },
  computed: {
    ...mapState(['hasUserInfo'])
  },
  methods: {
    // 意见反馈
    newProblem() {
      if (!this.hasUserInfo) {
        uni.reLaunch({
          url: '/pages/authorization/index'
        });
        return;
      }
      uni.navigateTo({
        url: '/mineInfo/problemFeedback/problemFeedback'
      });
    }
  }
};
</script>

<style lang="less">
.movable-area {
  // 保持在右下角
  margin-top: 200rpx;
  margin-left: 100rpx;
  height: calc(100vh - 300rpx); 
  width: calc(100vw - 150rpx);
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  position: fixed;
  z-index: 99999999;
  pointer-events: none; //此处要加,鼠标事件可以渗透
  .movable-view {
    width: 100rpx; 
    height: 100rpx;
    pointer-events: auto; //恢复鼠标事件
    .newProblem {
      width: 100rpx;
      height: 100rpx;
      display: flex;
      justify-content: center;
      flex-direction: column;
      align-items: center;
      background: #ffffff;
      border-radius: 50%;
      border: 1px solid #a6a6a6;
      image {
        width: 30rpx;
        height: 30rpx;
      }

      text {
        margin-top: 10rpx;
        font-size: 24rpx;
        font-family: PingFangSC-Semibold, PingFang SC;
        font-weight: 600;
        color: #000000;
      }
    }
  }
}
</style>

2.在main.js中进行挂载,之后每个页面不需要引入

默认有easycom机制,可跳过

import App from './App'
import Vue from 'vue'
import suspend from '@/components/suspend.vue' // 引入组件
 
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
    ...App
})
Vue.component('suspend',suspend)

// 进行全局挂载 
app.$mount()

3.使用组件(需要的页面添加即可)

<template>
<view class="content">
    <!--组件引用-->
    <suspend></suspend>
</view>
</template>

4.记录拖拽位置同步vuex

每个页面保持一致

<template>
  <view>
    <movable-area class="movable-area">
      <movable-view
        class="movable-view"
        :x="moveX"
        :y="moveY"
        direction="all"
        @change="handleMove"
      >
        <!-- <view class="newProblem" @click="newProblem">
          <image src="https://dl.essmall.cn/minapp/static/fknew.png"></image>
          <text>反馈</text>
        </view> -->
        <view class="box">
          <view class="newProblem" @click="service">
            <image src="https://dl.essmall.cn/minapp/static/kf.png"></image>
            <text>客服</text>
          </view>
        </view>
      </movable-view>
    </movable-area>
  </view>
</template>

<script>
import { mapState } from "vuex";
export default {
  data() {
    return {
      x: 1000,
      y: 1000,
      timer: null,
    };
  },
  computed: {
    ...mapState(["hasUserInfo", "moveX", "moveY"]),
  },
  methods: {
    // 客服
    service() {
      if (!this.hasUserInfo) {
        uni.reLaunch({
          url: "/pages/authorization/index",
        });
        return;
      }
      uni.navigateTo({
        url: "/mineInfo/service/service",
      });
    },
    handleMove(event) {
      // 防抖
      clearTimeout(this.timer);
      this.timer = setTimeout(() => {
        const { x, y } = event.detail;
        this.$store.commit("changeMoveXY", { x, y });
      }, 200);
    },
  },
};
</script>

<style lang="less">
.movable-area {
  height: 100vh;
  width: 100vw;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  position: fixed;
  z-index: 99999999;
  pointer-events: none;
  .movable-view {
    width: 200rpx;
    height: 200rpx;
    pointer-events: auto;
    .box {
      width: 200rpx;
      height: 200rpx;
      display: flex;
      justify-content: center;
      align-items: center;
      .newProblem {
        width: 100rpx;
        height: 100rpx;
        display: flex;
        justify-content: center;
        flex-direction: column;
        align-items: center;
        background: #ffffff;
        border-radius: 50%;
        border: 1px solid #a6a6a6;
        image {
          width: 30rpx;
          height: 30rpx;
        }
      
        text {
          margin-top: 10rpx;
          font-size: 24rpx;
          font-family: PingFangSC-Semibold, PingFang SC;
          font-weight: 600;
          color: #000000;
        }
      }
    }
  }
}
</style>

5.有自定义导航栏的特殊处理


/***
    使用组件
    isCustom: 是否自定义导航栏
    statusBarHeight: 处理状态栏
    <suspend :isCustom="true" :statusBarHeight="60"></suspend>
***/

<template>
  <view>
    <movable-area class="movable-area" :style="{'margin-top': isCustom ? 'calc(88rpx + ' + statusBarHeight + 'rpx)' : '0px', 'height' : isCustom ? 'calc(100vh - 90rpx - '+ statusBarHeight + 'rpx' +')': 'calc(100vh - 2rpx)' }">
      <movable-view
        class="movable-view"
        :x="moveX"
        :y="moveY"
        direction="all"
        @change="handleMove"
      >
        <!-- <view class="newProblem" @click="newProblem">
          <image src="https://dl.essmall.cn/minapp/static/fknew.png"></image>
          <text>反馈</text>
        </view> -->
        <view class="box">
          <view class="newProblem" @click="service">
            <image src="https://dl.essmall.cn/minapp/static/kf.png"></image>
            <text>客服</text>
          </view>
        </view>
      </movable-view>
    </movable-area>
  </view>
</template>

<script>
import { mapState } from "vuex";
export default {
  props: {
    isCustom: {
      type: Boolean,
      default: false,
    },
    statusBarHeight: {
      type: Number,
      default: 0,
    },
  },
  data() {
    return {
      x: 1000,
      y: 1000,
      timer: null,
    };
  },
  computed: {
    ...mapState(["hasUserInfo", "moveX", "moveY"]),
  },
  methods: {
    // 客服
    service() {
      if (!this.hasUserInfo) {
        uni.reLaunch({
          url: "/pages/authorization/index",
        });
        return;
      }
      uni.navigateTo({
        url: "/mineInfo/service/service",
      });
    },
    handleMove(event) {
      clearTimeout(this.timer);
      this.timer = setTimeout(() => {
        const { x, y } = event.detail;
        this.$store.commit("changeMoveXY", { x, y });
      }, 200);
    },
  },
};
</script>

<style lang="less">
.movable-area {
  background-color: red;
  height: 100vh;
  width: 100vw;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  position: fixed;
  z-index: 99999999;
  pointer-events: none;
  .movable-view {
    width: 200rpx;
    height: 200rpx;
    pointer-events: auto;
    .box {
      width: 200rpx;
      height: 200rpx;
      display: flex;
      justify-content: center;
      align-items: center;
      .newProblem {
        width: 100rpx;
        height: 100rpx;
        display: flex;
        justify-content: center;
        flex-direction: column;
        align-items: center;
        background: #ffffff;
        border-radius: 50%;
        border: 1px solid #a6a6a6;
        image {
          width: 30rpx;
          height: 30rpx;
        }
      
        text {
          margin-top: 10rpx;
          font-size: 24rpx;
          font-family: PingFangSC-Semibold, PingFang SC;
          font-weight: 600;
          color: #000000;
        }
      }
    }
  }
}
</style>