|
|
@@ -1,9 +1,13 @@
|
|
1
|
1
|
<script lang="ts" setup>
|
|
|
2
|
+import { onMounted, onUnmounted, ref } from 'vue';
|
|
|
3
|
+
|
|
|
4
|
+// @ts-ignore
|
|
|
5
|
+import { useVbenModal } from '@vben/common-ui';
|
|
2
|
6
|
import { useElementPlusDesignTokens } from '@vben/hooks';
|
|
|
7
|
+
|
|
3
|
8
|
import { ElConfigProvider } from 'element-plus';
|
|
|
9
|
+
|
|
4
|
10
|
import { elementLocale } from '#/locales';
|
|
5
|
|
-// @ts-ignore
|
|
6
|
|
-import { useVbenModal } from '@vben/common-ui';
|
|
7
|
11
|
|
|
8
|
12
|
defineOptions({ name: 'App' });
|
|
9
|
13
|
|
|
|
@@ -13,15 +17,116 @@ const [AIModal, AIModalApi] = useVbenModal({
|
|
13
|
17
|
title: 'AI Assistant',
|
|
14
|
18
|
showCancelBtn: false,
|
|
15
|
19
|
});
|
|
16
|
|
-const handleAIModal = () => {
|
|
|
20
|
+const handleAIModal = (e: MouseEvent) => {
|
|
|
21
|
+ // 如果是拖动操作,不触发点击事件
|
|
|
22
|
+ if (shouldPreventClick.value) {
|
|
|
23
|
+ shouldPreventClick.value = false;
|
|
|
24
|
+ return;
|
|
|
25
|
+ }
|
|
17
|
26
|
AIModalApi.open();
|
|
18
|
27
|
};
|
|
|
28
|
+
|
|
|
29
|
+// 拖动相关状态
|
|
|
30
|
+const isDragging = ref(false);
|
|
|
31
|
+const startX = ref(0);
|
|
|
32
|
+const startY = ref(0);
|
|
|
33
|
+const position = ref({ x: 0, y: 0 });
|
|
|
34
|
+const aiIconRef = ref<HTMLElement | null>(null);
|
|
|
35
|
+const shouldPreventClick = ref(false);
|
|
|
36
|
+const moveThreshold = 5; // 拖动阈值,超过此值不触发点击
|
|
|
37
|
+
|
|
|
38
|
+// 初始化位置
|
|
|
39
|
+onMounted(() => {
|
|
|
40
|
+ // 默认位置:右下角
|
|
|
41
|
+ if (aiIconRef.value) {
|
|
|
42
|
+ const rect = aiIconRef.value.getBoundingClientRect();
|
|
|
43
|
+ position.value = {
|
|
|
44
|
+ x: window.innerWidth - rect.width - 30,
|
|
|
45
|
+ y: window.innerHeight - rect.height - 30,
|
|
|
46
|
+ };
|
|
|
47
|
+ }
|
|
|
48
|
+});
|
|
|
49
|
+
|
|
|
50
|
+// 鼠标按下事件
|
|
|
51
|
+const onMouseDown = (e: MouseEvent) => {
|
|
|
52
|
+ if (
|
|
|
53
|
+ e.target === aiIconRef.value ||
|
|
|
54
|
+ aiIconRef.value?.contains(e.target as Node)
|
|
|
55
|
+ ) {
|
|
|
56
|
+ isDragging.value = true;
|
|
|
57
|
+ startX.value = e.clientX;
|
|
|
58
|
+ startY.value = e.clientY;
|
|
|
59
|
+ shouldPreventClick.value = false;
|
|
|
60
|
+ }
|
|
|
61
|
+};
|
|
|
62
|
+
|
|
|
63
|
+// 鼠标移动事件
|
|
|
64
|
+const onMouseMove = (e: MouseEvent) => {
|
|
|
65
|
+ if (isDragging.value) {
|
|
|
66
|
+ // 计算移动距离
|
|
|
67
|
+ const deltaX = e.clientX - startX.value;
|
|
|
68
|
+ const deltaY = e.clientY - startY.value;
|
|
|
69
|
+
|
|
|
70
|
+ // 如果移动距离超过阈值,标记为拖动操作
|
|
|
71
|
+ if (Math.abs(deltaX) > moveThreshold || Math.abs(deltaY) > moveThreshold) {
|
|
|
72
|
+ shouldPreventClick.value = true;
|
|
|
73
|
+ }
|
|
|
74
|
+
|
|
|
75
|
+ // 更新位置
|
|
|
76
|
+ position.value = {
|
|
|
77
|
+ x: position.value.x + deltaX,
|
|
|
78
|
+ y: position.value.y + deltaY,
|
|
|
79
|
+ };
|
|
|
80
|
+
|
|
|
81
|
+ // 更新起始位置,用于下一次移动计算
|
|
|
82
|
+ startX.value = e.clientX;
|
|
|
83
|
+ startY.value = e.clientY;
|
|
|
84
|
+
|
|
|
85
|
+ // 限制在窗口范围内
|
|
|
86
|
+ if (aiIconRef.value) {
|
|
|
87
|
+ const rect = aiIconRef.value.getBoundingClientRect();
|
|
|
88
|
+ position.value.x = Math.max(
|
|
|
89
|
+ 0,
|
|
|
90
|
+ Math.min(position.value.x, window.innerWidth - rect.width),
|
|
|
91
|
+ );
|
|
|
92
|
+ position.value.y = Math.max(
|
|
|
93
|
+ 0,
|
|
|
94
|
+ Math.min(position.value.y, window.innerHeight - rect.height),
|
|
|
95
|
+ );
|
|
|
96
|
+ }
|
|
|
97
|
+ }
|
|
|
98
|
+};
|
|
|
99
|
+
|
|
|
100
|
+// 鼠标释放事件
|
|
|
101
|
+const onMouseUp = () => {
|
|
|
102
|
+ isDragging.value = false;
|
|
|
103
|
+};
|
|
|
104
|
+
|
|
|
105
|
+// 添加和移除全局事件监听器
|
|
|
106
|
+onMounted(() => {
|
|
|
107
|
+ document.addEventListener('mousemove', onMouseMove);
|
|
|
108
|
+ document.addEventListener('mouseup', onMouseUp);
|
|
|
109
|
+});
|
|
|
110
|
+
|
|
|
111
|
+onUnmounted(() => {
|
|
|
112
|
+ document.removeEventListener('mousemove', onMouseMove);
|
|
|
113
|
+ document.removeEventListener('mouseup', onMouseUp);
|
|
|
114
|
+});
|
|
19
|
115
|
</script>
|
|
20
|
116
|
|
|
21
|
117
|
<template>
|
|
22
|
118
|
<ElConfigProvider :locale="elementLocale">
|
|
23
|
119
|
<RouterView />
|
|
24
|
|
- <div class="ai-icon" @click="handleAIModal">
|
|
|
120
|
+ <div
|
|
|
121
|
+ ref="aiIconRef"
|
|
|
122
|
+ class="ai-icon"
|
|
|
123
|
+ @click="handleAIModal"
|
|
|
124
|
+ @mousedown="onMouseDown"
|
|
|
125
|
+ :style="{
|
|
|
126
|
+ left: `${position.x}px`,
|
|
|
127
|
+ top: `${position.y}px`,
|
|
|
128
|
+ }"
|
|
|
129
|
+ >
|
|
25
|
130
|
<img src="/AIimgs.gif" alt="AI Assistant" />
|
|
26
|
131
|
</div>
|
|
27
|
132
|
<AIModal />
|
|
|
@@ -31,8 +136,6 @@ const handleAIModal = () => {
|
|
31
|
136
|
<style scoped lang="scss">
|
|
32
|
137
|
.ai-icon {
|
|
33
|
138
|
position: fixed;
|
|
34
|
|
- bottom: 30px;
|
|
35
|
|
- right: 30px;
|
|
36
|
139
|
z-index: 9999;
|
|
37
|
140
|
width: 100px;
|
|
38
|
141
|
height: 100px;
|
|
|
@@ -42,6 +145,11 @@ const handleAIModal = () => {
|
|
42
|
145
|
align-items: center;
|
|
43
|
146
|
justify-content: center;
|
|
44
|
147
|
cursor: pointer;
|
|
|
148
|
+ user-select: none;
|
|
|
149
|
+ /* 拖动时的光标样式 */
|
|
|
150
|
+ &:active {
|
|
|
151
|
+ cursor: grabbing;
|
|
|
152
|
+ }
|
|
45
|
153
|
}
|
|
46
|
154
|
|
|
47
|
155
|
.ai-icon img {
|