Server.vue 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. <template>
  2. <view>
  3. <view
  4. class="serve"
  5. :style="{ left: left + 'rpx', top: top + 'rpx' }"
  6. @touchstart="onTouchStart"
  7. @touchmove="onTouchMove"
  8. @touchend="onTouchEnd"
  9. >
  10. <view class="serve-content">
  11. <image class="w130 h130"
  12. src="/static/images/server.png"
  13. mode="aspectFit"></image>
  14. <text class="text">官方客服</text>
  15. <button class="contact-btn" open-type="contact"></button>
  16. </view>
  17. </view>
  18. </view>
  19. </template>
  20. <script>
  21. export default {
  22. name:"Server",
  23. data() {
  24. return {
  25. left: 0,
  26. top: 0,
  27. startX: 0,
  28. startY: 0,
  29. isDragging: false,
  30. screenWidth: 0,
  31. screenHeight: 0
  32. };
  33. },
  34. mounted() {
  35. this.getSystemInfo();
  36. this.initPosition();
  37. },
  38. methods: {
  39. // 初始化位置
  40. initPosition() {
  41. // 从本地存储加载位置
  42. const position = uni.getStorageSync('server_position');
  43. if (position) {
  44. this.left = position.left;
  45. this.top = position.top;
  46. } else {
  47. // 默认位置:右侧80%高度
  48. this.left = 750 - 150 - 20; // 屏幕宽度 - 容器宽度 - 右边距
  49. this.top = this.px2rpx(this.screenHeight * 0.8 - 75); // 80%高度,居中偏移
  50. }
  51. },
  52. // 获取系统信息
  53. getSystemInfo() {
  54. uni.getSystemInfo({
  55. success: (res) => {
  56. this.screenWidth = res.windowWidth;
  57. this.screenHeight = res.windowHeight;
  58. }
  59. });
  60. },
  61. // 触摸开始
  62. onTouchStart(e) {
  63. // 阻止事件冒泡,不影响其他元素
  64. e.stopPropagation();
  65. e.preventDefault();
  66. this.startX = e.touches[0].clientX;
  67. this.startY = e.touches[0].clientY;
  68. this.isDragging = false;
  69. },
  70. // 触摸移动
  71. onTouchMove(e) {
  72. // 阻止事件冒泡和默认行为
  73. e.stopPropagation();
  74. e.preventDefault();
  75. if (!this.startX || !this.startY) return;
  76. const currentX = e.touches[0].clientX;
  77. const currentY = e.touches[0].clientY;
  78. const diffX = currentX - this.startX;
  79. const diffY = currentY - this.startY;
  80. // 判断是否开始拖拽(移动超过5px)
  81. if (!this.isDragging && (Math.abs(diffX) > 5 || Math.abs(diffY) > 5)) {
  82. this.isDragging = true;
  83. }
  84. if (this.isDragging) {
  85. // 计算新的位置(rpx)
  86. const newLeft = this.rpx2px(this.left) + diffX;
  87. const newTop = this.rpx2px(this.top) + diffY;
  88. // 边界检查
  89. const boundedLeft = Math.max(10, Math.min(newLeft, this.screenWidth - this.rpx2px(150)));
  90. const boundedTop = Math.max(10, Math.min(newTop, this.screenHeight - this.rpx2px(150)));
  91. // 转换回rpx
  92. this.left = this.px2rpx(boundedLeft);
  93. this.top = this.px2rpx(boundedTop);
  94. // 更新起始位置
  95. this.startX = currentX;
  96. this.startY = currentY;
  97. }
  98. },
  99. // 触摸结束
  100. onTouchEnd(e) {
  101. // 阻止事件冒泡
  102. e.stopPropagation();
  103. e.preventDefault();
  104. if (this.isDragging) {
  105. // 吸边效果
  106. this.attachToEdge();
  107. // 保存位置
  108. this.savePosition();
  109. }
  110. this.startX = 0;
  111. this.startY = 0;
  112. this.isDragging = false;
  113. },
  114. // 吸边效果
  115. attachToEdge() {
  116. const containerWidth = 150; // 容器宽度
  117. const screenWidthRpx = 750;
  118. const centerX = screenWidthRpx / 2;
  119. // 根据当前位置决定吸附到左边还是右边
  120. if (this.left < centerX) {
  121. this.left = 20; // 吸附到左边
  122. } else {
  123. this.left = screenWidthRpx - containerWidth - 20; // 吸附到右边
  124. }
  125. },
  126. // 保存位置到本地存储
  127. savePosition() {
  128. uni.setStorageSync('server_position', {
  129. left: this.left,
  130. top: this.top
  131. });
  132. },
  133. // rpx转px
  134. rpx2px(rpx) {
  135. return rpx / 750 * this.screenWidth;
  136. },
  137. // px转rpx
  138. px2rpx(px) {
  139. return px * 750 / this.screenWidth;
  140. }
  141. }
  142. }
  143. </script>
  144. <style lang="scss" scoped>
  145. .serve{
  146. position: fixed;
  147. z-index: 9999;
  148. width: 150rpx;
  149. height: 150rpx;
  150. // 确保拖拽时不影响其他元素
  151. touch-action: none;
  152. user-select: none;
  153. -webkit-user-select: none;
  154. .serve-content {
  155. width: 100%;
  156. height: 100%;
  157. display: flex;
  158. flex-direction: column;
  159. align-items: center;
  160. justify-content: center;
  161. position: relative;
  162. // 拖拽时的视觉反馈
  163. &:active {
  164. opacity: 0.9;
  165. transition: opacity 0.2s ease;
  166. }
  167. }
  168. .w130 {
  169. width: 130rpx !important;
  170. height: 130rpx !important;
  171. flex-shrink: 0; // 防止图片变形
  172. }
  173. .h130 {
  174. width: 130rpx !important;
  175. height: 130rpx !important;
  176. flex-shrink: 0; // 防止图片变形
  177. }
  178. .text{
  179. margin-top: 8rpx;
  180. font-weight: 500;
  181. font-size: 24rpx;
  182. color: #333;
  183. background: rgba(255, 255, 255, 0.9);
  184. padding: 6rpx 12rpx;
  185. border-radius: 20rpx;
  186. white-space: nowrap;
  187. flex-shrink: 0; // 防止文字变形
  188. border: 1rpx solid rgba(0, 0, 0, 0.1);
  189. backdrop-filter: blur(10rpx);
  190. }
  191. .contact-btn {
  192. position: absolute;
  193. top: 0;
  194. left: 0;
  195. width: 100%;
  196. height: 100%;
  197. opacity: 0;
  198. z-index: 10;
  199. // 确保按钮不变形
  200. border: none;
  201. border-radius: 0;
  202. background: transparent;
  203. padding: 0;
  204. margin: 0;
  205. &::after {
  206. border: none;
  207. }
  208. }
  209. }
  210. // 防止拖拽时页面滚动
  211. page {
  212. overflow: hidden;
  213. height: 100%;
  214. }
  215. </style>