ut-suspension.vue 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. <template>
  2. <movable-area v-if="inited" class="suspension-area" :style="areaStyle">
  3. <movable-view
  4. class="suspension-view"
  5. direction="all"
  6. :x="x"
  7. :y="y"
  8. :inertia="inertia"
  9. :out-of-bounds="true"
  10. :disabled="false"
  11. :scale="false"
  12. :style="{
  13. width: width,
  14. height: height,
  15. }"
  16. @click="handleClick">
  17. <slot>
  18. <image src="https://yujin-szyy.oss-cn-chengdu.aliyuncs.com/szyy/images-plt/common/btn_add_logo.png" mode="widthFix" class="w-120 h-120"></image>
  19. </slot>
  20. </movable-view>
  21. </movable-area>
  22. </template>
  23. <script setup lang="ts">
  24. /**
  25. * Props
  26. * - width: 组件宽度字符串,支持 rpx/px,例如 '120rpx'
  27. * - height: 组件高度字符串,支持 rpx/px,例如 '120rpx'
  28. * - right: 距右侧偏移,支持 rpx/px,例如 '20rpx'
  29. * - bottom: 距底部偏移,支持 rpx/px,例如 '200rpx'
  30. * - inertia/snapThreshold/bgColor/borderRadius: 其他配置
  31. * 行为:初始化后根据屏幕宽高与 right/bottom 计算 x/y,默认右下角,避免渲染闪动
  32. */
  33. // 定义props
  34. interface Props {
  35. width?: string; // 宽度 rpx/px 字符串
  36. height?: string; // 高度 rpx/px 字符串
  37. right?: string; // 距右侧 rpx/px 字符串
  38. bottom?: string; // 距底部 rpx/px 字符串
  39. inertia?: boolean;
  40. bgColor?: string;
  41. borderRadius?: string;
  42. }
  43. const props = withDefaults(defineProps<Props>(), {
  44. width: '120rpx', // 默认宽度
  45. height: '120rpx', // 默认高度
  46. right: '10rpx', // 默认右侧间距
  47. bottom: '280rpx', // 默认底部间距
  48. inertia: false,
  49. bgColor: 'transparent',
  50. borderRadius: '0',
  51. });
  52. // 响应式状态
  53. const x = ref<number>(0); // movable-view的x坐标(距离左边)
  54. const y = ref<number>(0); // movable-view的y坐标(距离顶部)
  55. const inited = ref<boolean>(false); // 控制渲染时机,避免闪动
  56. // 屏幕尺寸
  57. const screenWidth = ref<number>(0);
  58. const screenHeight = ref<number>(0);
  59. // 将字符串尺寸转换为像素(px),使用 uView 的换算
  60. function toPx(val?: string | number): number {
  61. if (val === undefined || val === null) return 0;
  62. if (typeof val === 'number') return val;
  63. return (uni as any).$u.getPx(val as string);
  64. }
  65. // movable-area 样式
  66. const areaStyle = computed(() => {
  67. return {
  68. width: `${screenWidth.value}px`,
  69. height: `${screenHeight.value}px`,
  70. position: 'fixed',
  71. top: '0',
  72. left: '0',
  73. zIndex: '999',
  74. };
  75. });
  76. // 初始化时获取屏幕尺寸
  77. onMounted(() => {
  78. const systemInfo = uni.getSystemInfoSync();
  79. screenWidth.value = systemInfo.windowWidth;
  80. screenHeight.value = systemInfo.windowHeight;
  81. // 根据 right/bottom 计算初始位置(默认右下角)
  82. const widthPx = toPx(props.width);
  83. const heightPx = toPx(props.height);
  84. const rightPx = toPx(props.right);
  85. const bottomPx = toPx(props.bottom);
  86. x.value = Math.max(0, screenWidth.value - rightPx - widthPx);
  87. y.value = Math.max(0, screenHeight.value - bottomPx - heightPx);
  88. inited.value = true;
  89. });
  90. // click 事件抛出
  91. const emit = defineEmits<{
  92. (e: 'click', payload: { x: number; y: number }): void;
  93. }>();
  94. // 点击事件
  95. const handleClick = () => {
  96. emit('click', { x: x.value, y: y.value });
  97. };
  98. </script>
  99. <style>
  100. .suspension-area {
  101. /* 移除 pointer-events: none,确保可以正常接收拖动事件 */
  102. pointer-events: none;
  103. }
  104. .suspension-view {
  105. display: flex;
  106. justify-content: center;
  107. align-items: center;
  108. overflow: hidden;
  109. background-color: v-bind('props.bgColor');
  110. border-radius: v-bind('props.borderRadius');
  111. pointer-events: auto;
  112. }
  113. </style>