Vue2+TS封装一个可全局拖拽的弹窗组件

8/1/2023 vue

# Vue2+TS封装一个可全局拖拽的弹窗组件

ant组件的弹窗组件不支持拖拽,就很难受 项目里刚好有这个需求,就自己封装了一个

效果图: 在这里插入图片描述

# vue部分:

<template>
  <div class="image-standard-modal"
       :style="{ top: top + 'px', left: left + 'px' }"
       v-show="visible"
       v-on:mouseenter="onMouseEnter(true)"
       v-on:mouseleave="onMouseLeave">
    <div v-on:mouseenter="onMouseEnter(true)">
      <div class="header"
           v-on:mouseenter="onMouseEnter(true)">
        <span class="title">{{ title }}</span>
        <a-icon class="float-right"
                style="font-size: 18px;"
                type="close" />
        <a-divider />
      </div>
      <div class="body"
           v-on:mouseenter="onMouseEnter(false)">
        <slot></slot>
      </div>
    </div>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# script部分:

<script lang="ts">
import {
  Vue, Component, Prop, Emit,
} from 'vue-property-decorator';
import GBackstageBreadcrumb from '@/components/common/GBackstageBreadcrumb.vue';

@Component({
  name: 'DragAndDropModal',
  components: { BackstageBreadcrumb: GBackstageBreadcrumb },
})
export default class DragAndDropModal extends Vue {
  @Prop({ default: true }) visible!: boolean;

  @Prop({ default: '影像标准信息' }) title!: string;

  top = 200; // 弹窗的垂直位置

  left: any = '50%'; // 弹窗的水平位置

  dragging = false; // 是否正在拖拽

  diffX = 0; // 鼠标在弹窗内的水平偏移量

  diffY = 0; // 鼠标在弹窗内的垂直偏移量

  // 鼠标进入弹窗区域
  handleMouseDown(event) {
    event.preventDefault();
    this.dragging = true;
    this.diffX = event.clientX - event.target.getBoundingClientRect().left;
    this.diffY = event.clientY - event.target.getBoundingClientRect().top;
  }

  // 鼠标移动事件
  handleMouseMove(event) {
    if (this.dragging) {
      this.left = event.clientX - this.diffX;
      this.top = event.clientY - this.diffY;
    }
  }

  // 鼠标松开事件
  handleMouseUp() {
    this.dragging = false;
  }

  // 鼠标进入弹窗区域注册监听鼠标
  onMouseEnter(type) {
    console.log(type);
    if (type) {
      document.addEventListener('mousedown', this.handleMouseDown);
      document.addEventListener('mousemove', this.handleMouseMove);
      document.addEventListener('mouseup', this.handleMouseUp);
    } else {
      // 鼠标离开弹窗区域取消监听鼠标
      this.onMouseLeave();
    }
  }

  // 鼠标离开弹窗区域取消监听鼠标
  onMouseLeave() {
    if (this.dragging) {
      return;
    }
    document.removeEventListener('mousedown', this.handleMouseDown);
    document.removeEventListener('mousemove', this.handleMouseMove);
    document.removeEventListener('mouseup', this.handleMouseUp);
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

# style部分:

<style scoped lang='less'>
// 分割线
.ant-divider-horizontal {
  margin: 24px 0 0 0 !important;
}
.image-standard-modal{
  position: fixed; // 定位到最外层
  top: 100;
  left: 100;
  min-width: 400px;
  z-index: 1000;
  padding: 98px 24px 24px 24px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  border-radius: 4px;
  background-color: rgba(255, 255, 255, 0.8);
  .header {
    position: absolute;
    top: 0;
    left: 0;
    padding: 24px 24px 0 24px;
    background-color: white;
    width: 100%;
    .title {
      font-size: 16px;
      font-weight: 600;
      color: #333;
    }
  }
  .body{
    min-height: 200px;
    max-height: 70vh;
  }
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 完整代码:

<template>
  <div class="image-standard-modal"
       :style="{ top: top + 'px', left: left + 'px' }"
       v-show="visible"
       v-on:mouseenter="onMouseEnter(true)"
       v-on:mouseleave="onMouseLeave">
    <div v-on:mouseenter="onMouseEnter(true)">
      <div class="header"
           v-on:mouseenter="onMouseEnter(true)">
        <span class="title">{{ title }}</span>
        <a-icon class="float-right"
                style="font-size: 18px;"
                type="close" />
        <a-divider />
      </div>
      <div class="body"
           v-on:mouseenter="onMouseEnter(false)">
        <slot></slot>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import {
  Vue, Component, Prop, Emit,
} from 'vue-property-decorator';
import GBackstageBreadcrumb from '@/components/common/GBackstageBreadcrumb.vue';

@Component({
  name: 'DragAndDropModal',
  components: { BackstageBreadcrumb: GBackstageBreadcrumb },
})
export default class DragAndDropModal extends Vue {
  @Prop({ default: true }) visible!: boolean;

  @Prop({ default: '影像标准信息' }) title!: string;

  top = 200; // 弹窗的垂直位置

  left: any = '50%'; // 弹窗的水平位置

  dragging = false; // 是否正在拖拽

  diffX = 0; // 鼠标在弹窗内的水平偏移量

  diffY = 0; // 鼠标在弹窗内的垂直偏移量

  // 鼠标进入弹窗区域
  handleMouseDown(event) {
    event.preventDefault();
    this.dragging = true;
    this.diffX = event.clientX - event.target.getBoundingClientRect().left;
    this.diffY = event.clientY - event.target.getBoundingClientRect().top;
  }

  // 鼠标移动事件
  handleMouseMove(event) {
    if (this.dragging) {
      this.left = event.clientX - this.diffX;
      this.top = event.clientY - this.diffY;
    }
  }

  // 鼠标松开事件
  handleMouseUp() {
    this.dragging = false;
  }

  // 鼠标进入弹窗区域注册监听鼠标
  onMouseEnter(type) {
    console.log(type);
    if (type) {
      document.addEventListener('mousedown', this.handleMouseDown);
      document.addEventListener('mousemove', this.handleMouseMove);
      document.addEventListener('mouseup', this.handleMouseUp);
    } else {
      // 鼠标离开弹窗区域取消监听鼠标
      this.onMouseLeave();
    }
  }

  // 鼠标离开弹窗区域取消监听鼠标
  onMouseLeave() {
    if (this.dragging) {
      return;
    }
    document.removeEventListener('mousedown', this.handleMouseDown);
    document.removeEventListener('mousemove', this.handleMouseMove);
    document.removeEventListener('mouseup', this.handleMouseUp);
  }
}
</script>

<style scoped lang='less'>
// 分割线
.ant-divider-horizontal {
  margin: 24px 0 0 0 !important;
}

.image-standard-modal {
  position: fixed; // 定位到最外层
  top: 100;
  left: 100;
  min-width: 400px;
  z-index: 1000;
  padding: 98px 24px 24px 24px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  border-radius: 4px;
  background-color: rgba(255, 255, 255, 0.8);

  .header {
    position: absolute;
    top: 0;
    left: 0;
    padding: 24px 24px 0 24px;
    background-color: white;
    width: 100%;

    .title {
      font-size: 16px;
      font-weight: 600;
      color: #333;
    }
  }

  .body {
    min-height: 150px;
    max-height: 70vh;
  }
}
</style>


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134

# 在其他组件使用:

    // 引入
    import DragAndDropModal from '@/map/DragAndDropModal.vue';
    
    // 注册
    @Component({
  	  name: 'OpenLayerMapRightLayout',
 	  components: {
        DragAndDropModal,
     },
   })
 	// 使用
    <DragAndDropModal>可拖拽弹窗</DragAndDropModal>
1
2
3
4
5
6
7
8
9
10
11
12