MoveablePanel.vue 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. <template>
  2. <div
  3. class="moveable-panel"
  4. ref="moveablePanelRef"
  5. :style="{
  6. width: w + 'px',
  7. height: h ? h + 'px' : 'auto',
  8. left: x + 'px',
  9. top: y + 'px',
  10. }"
  11. >
  12. <template v-if="title">
  13. <div class="header" @mousedown="$event => startMove($event)">
  14. <div class="title">{{title}}</div>
  15. <div class="close-btn" @click="emit('close')"><IconClose /></div>
  16. </div>
  17. <div class="content">
  18. <slot></slot>
  19. </div>
  20. </template>
  21. <div v-else class="content" @mousedown="$event => startMove($event)">
  22. <slot></slot>
  23. </div>
  24. <div class="resizer" v-if="resizeable" @mousedown="$event => startResize($event)"></div>
  25. </div>
  26. </template>
  27. <script lang="ts" setup>
  28. import { computed, onMounted, ref, useTemplateRef } from 'vue'
  29. const props = withDefaults(defineProps<{
  30. width: number
  31. height: number
  32. minWidth?: number
  33. minHeight?: number
  34. maxWidth?: number
  35. maxHeight?: number
  36. left?: number
  37. top?: number
  38. title?: string
  39. moveable?: boolean
  40. resizeable?: boolean
  41. }>(), {
  42. minWidth: 20,
  43. minHeight: 20,
  44. maxWidth: 500,
  45. maxHeight: 500,
  46. left: 10,
  47. top: 10,
  48. title: '',
  49. moveable: true,
  50. resizeable: false,
  51. })
  52. const emit = defineEmits<{
  53. (event: 'close'): void
  54. }>()
  55. const x = ref(0)
  56. const y = ref(0)
  57. const w = ref(0)
  58. const h = ref(0)
  59. const moveablePanelRef = useTemplateRef<HTMLElement>('moveablePanelRef')
  60. const realHeight = computed(() => {
  61. if (!h.value) {
  62. return moveablePanelRef.value?.clientHeight || 0
  63. }
  64. return h.value
  65. })
  66. onMounted(() => {
  67. if (props.left >= 0) x.value = props.left
  68. else x.value = document.body.clientWidth + props.left - props.width
  69. if (props.top >= 0) y.value = props.top
  70. else y.value = document.body.clientHeight + props.top - (props.height || realHeight.value)
  71. w.value = props.width
  72. h.value = props.height
  73. })
  74. const startMove = (e: MouseEvent) => {
  75. if (!props.moveable) return
  76. let isMouseDown = true
  77. const windowWidth = document.body.clientWidth
  78. const clientHeight = document.body.clientHeight
  79. const startPageX = e.pageX
  80. const startPageY = e.pageY
  81. const originLeft = x.value
  82. const originTop = y.value
  83. document.onmousemove = e => {
  84. if (!isMouseDown) return
  85. const moveX = e.pageX - startPageX
  86. const moveY = e.pageY - startPageY
  87. let left = originLeft + moveX
  88. let top = originTop + moveY
  89. if (left < 0) left = 0
  90. if (top < 0) top = 0
  91. if (left + w.value > windowWidth) left = windowWidth - w.value
  92. if (top + realHeight.value > clientHeight) top = clientHeight - realHeight.value
  93. x.value = left
  94. y.value = top
  95. }
  96. document.onmouseup = () => {
  97. isMouseDown = false
  98. document.onmousemove = null
  99. document.onmouseup = null
  100. }
  101. }
  102. const startResize = (e: MouseEvent) => {
  103. if (!props.resizeable) return
  104. let isMouseDown = true
  105. const startPageX = e.pageX
  106. const startPageY = e.pageY
  107. const originWidth = w.value
  108. const originHeight = h.value
  109. document.onmousemove = e => {
  110. if (!isMouseDown) return
  111. const moveX = e.pageX - startPageX
  112. const moveY = e.pageY - startPageY
  113. let width = originWidth + moveX
  114. let height = originHeight + moveY
  115. if (width < props.minWidth) width = props.minWidth
  116. if (height < props.minHeight) height = props.minHeight
  117. if (width > props.maxWidth) width = props.maxWidth
  118. if (height > props.maxHeight) height = props.maxHeight
  119. w.value = width
  120. h.value = height
  121. }
  122. document.onmouseup = () => {
  123. isMouseDown = false
  124. document.onmousemove = null
  125. document.onmouseup = null
  126. }
  127. }
  128. </script>
  129. <style lang="scss" scoped>
  130. .moveable-panel {
  131. position: fixed;
  132. background-color: #fff;
  133. box-shadow: $boxShadow;
  134. border: 1px solid $borderColor;
  135. border-radius: $borderRadius;
  136. display: flex;
  137. flex-direction: column;
  138. z-index: 999;
  139. }
  140. .resizer {
  141. width: 10px;
  142. height: 10px;
  143. position: absolute;
  144. bottom: 0;
  145. right: 0;
  146. cursor: se-resize;
  147. &::after {
  148. content: "";
  149. position: absolute;
  150. bottom: -4px;
  151. right: -4px;
  152. transform: rotate(45deg);
  153. transform-origin: center;
  154. width: 0;
  155. height: 0;
  156. border: 6px solid transparent;
  157. border-left-color: #e1e1e1;
  158. }
  159. }
  160. .header {
  161. height: 40px;
  162. display: flex;
  163. align-items: center;
  164. border-bottom: 1px solid #f0f0f0;
  165. cursor: move;
  166. }
  167. .title {
  168. flex: 1;
  169. font-size: 13px;
  170. padding-left: 10px;
  171. }
  172. .close-btn {
  173. width: 40px;
  174. height: 40px;
  175. display: flex;
  176. justify-content: center;
  177. align-items: center;
  178. color: #666;
  179. font-size: 13px;
  180. cursor: pointer;
  181. }
  182. .content {
  183. flex: 1;
  184. padding: 10px;
  185. overflow: auto;
  186. }
  187. </style>