样式修改
This commit is contained in:
@@ -1,8 +1,14 @@
|
||||
<template>
|
||||
<div v-if="hasAgent">
|
||||
<!-- 悬浮球 -->
|
||||
<div class="ball-container">
|
||||
<div class="chat-ball" @click="openDrawer">
|
||||
<div
|
||||
v-show="!drawerVisible"
|
||||
class="ball-container"
|
||||
:style="ballStyle"
|
||||
@mousedown="startDrag"
|
||||
@touchstart="startDrag"
|
||||
>
|
||||
<div class="chat-ball" @click="handleBallClick">
|
||||
<img src="@/assets/imgs/chat-ball.svg" alt="AI助手" class="ball-icon" />
|
||||
<div v-if="unreadCount > 0" class="unread-badge">{{ unreadCount }}</div>
|
||||
</div>
|
||||
@@ -134,7 +140,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, nextTick } from 'vue';
|
||||
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { Close, Paperclip, VideoPause, Promotion } from '@element-plus/icons-vue';
|
||||
import { chatApi, chatHistoryApi, aiAgentConfigApi, fileUploadApi } from '@/apis/ai';
|
||||
@@ -160,15 +166,27 @@ const agentAvatarUrl = ref<string>('');
|
||||
|
||||
// ===== 悬浮球相关 =====
|
||||
const ballRef = ref<HTMLElement | null>(null);
|
||||
const ballX = ref(0);
|
||||
const ballX = ref(0); // 实际位置(像素)
|
||||
const ballY = ref(0);
|
||||
const unreadCount = ref(0);
|
||||
const isDragging = ref(false);
|
||||
const dragStartX = ref(0);
|
||||
const dragStartY = ref(0);
|
||||
|
||||
// 存储悬浮球的相对位置(百分比),用于窗口resize时保持相对位置
|
||||
const ballXPercent = ref(1); // 1 表示右侧
|
||||
const ballYPercent = ref(0.85); // 0.85 表示距离底部较近
|
||||
const isUserDragged = ref(false); // 标记用户是否手动拖动过
|
||||
|
||||
// 拖拽检测相关
|
||||
const dragStartPosX = ref(0); // 记录拖拽开始时的实际位置
|
||||
const dragStartPosY = ref(0);
|
||||
|
||||
// 悬浮球样式
|
||||
const ballStyle = computed(() => ({
|
||||
right: '20px',
|
||||
bottom: '100px',
|
||||
position: 'fixed'
|
||||
left: `${ballX.value}px`,
|
||||
top: `${ballY.value}px`,
|
||||
position: 'fixed' as const
|
||||
}));
|
||||
|
||||
// 打开抽屉
|
||||
@@ -176,7 +194,133 @@ function openDrawer() {
|
||||
drawerVisible.value = true;
|
||||
}
|
||||
|
||||
// ===== 对话相关 =====
|
||||
// 开始拖拽
|
||||
function startDrag(event: MouseEvent | TouchEvent) {
|
||||
isDragging.value = true;
|
||||
|
||||
const clientX = 'touches' in event ? event.touches[0].clientX : event.clientX;
|
||||
const clientY = 'touches' in event ? event.touches[0].clientY : event.clientY;
|
||||
|
||||
dragStartX.value = clientX - ballX.value;
|
||||
dragStartY.value = clientY - ballY.value;
|
||||
|
||||
// 记录起始位置,用于判断是点击还是拖拽
|
||||
dragStartPosX.value = ballX.value;
|
||||
dragStartPosY.value = ballY.value;
|
||||
|
||||
// 添加事件监听
|
||||
document.addEventListener('mousemove', onDrag);
|
||||
document.addEventListener('touchmove', onDrag, { passive: false });
|
||||
document.addEventListener('mouseup', endDrag);
|
||||
document.addEventListener('touchend', endDrag);
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
// 结束拖拽
|
||||
function endDrag() {
|
||||
if (!isDragging.value) return;
|
||||
|
||||
// 移除事件监听
|
||||
document.removeEventListener('mousemove', onDrag);
|
||||
document.removeEventListener('mouseup', endDrag);
|
||||
document.removeEventListener('touchmove', onDrag);
|
||||
document.removeEventListener('touchend', endDrag);
|
||||
|
||||
// 计算移动距离
|
||||
const moveDistanceX = Math.abs(ballX.value - dragStartPosX.value);
|
||||
const moveDistanceY = Math.abs(ballY.value - dragStartPosY.value);
|
||||
const totalDistance = Math.sqrt(moveDistanceX * moveDistanceX + moveDistanceY * moveDistanceY);
|
||||
|
||||
// 判断是点击还是拖拽(移动距离阈值为5px)
|
||||
const isClick = totalDistance <= 5;
|
||||
|
||||
if (isClick) {
|
||||
// 如果是点击,打开抽屉
|
||||
openDrawer();
|
||||
} else {
|
||||
// 如果是拖拽,执行吸附和位置调整
|
||||
isUserDragged.value = true;
|
||||
|
||||
const windowWidth = window.innerWidth;
|
||||
const windowHeight = window.innerHeight;
|
||||
const ballWidth = 40;
|
||||
const ballHeight = 40;
|
||||
const margin = 16;
|
||||
|
||||
// 自动吸附到左右两侧
|
||||
if (ballX.value < windowWidth / 2) {
|
||||
// 吸附到左侧
|
||||
ballX.value = margin;
|
||||
ballXPercent.value = 0;
|
||||
} else {
|
||||
// 吸附到右侧
|
||||
ballX.value = windowWidth - ballWidth - margin;
|
||||
ballXPercent.value = 1;
|
||||
}
|
||||
|
||||
// 限制垂直位置并保存百分比
|
||||
if (ballY.value < margin) {
|
||||
ballY.value = margin;
|
||||
} else if (ballY.value > windowHeight - ballHeight - margin) {
|
||||
ballY.value = windowHeight - ballHeight - margin;
|
||||
}
|
||||
|
||||
// 保存Y位置的百分比(以中心点计算)
|
||||
ballYPercent.value = (ballY.value + ballHeight / 2) / windowHeight;
|
||||
}
|
||||
|
||||
isDragging.value = false;
|
||||
}
|
||||
|
||||
// 拖拽过程
|
||||
function onDrag(event: MouseEvent | TouchEvent) {
|
||||
if (!isDragging.value) return;
|
||||
|
||||
const clientX = 'touches' in event ? event.touches[0].clientX : event.clientX;
|
||||
const clientY = 'touches' in event ? event.touches[0].clientY : event.clientY;
|
||||
|
||||
ballX.value = clientX - dragStartX.value;
|
||||
ballY.value = clientY - dragStartY.value;
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
// 根据百分比计算实际位置
|
||||
function updateBallPosition() {
|
||||
const windowWidth = window.innerWidth;
|
||||
const windowHeight = window.innerHeight;
|
||||
const ballWidth = 40;
|
||||
const ballHeight = 40;
|
||||
const margin = 16;
|
||||
|
||||
// 根据百分比计算位置
|
||||
if (ballXPercent.value < 0.5) {
|
||||
// 左侧
|
||||
ballX.value = margin;
|
||||
} else {
|
||||
// 右侧
|
||||
ballX.value = windowWidth - ballWidth - margin;
|
||||
}
|
||||
|
||||
// 计算Y位置,确保不超出边界
|
||||
let targetY = windowHeight * ballYPercent.value - ballHeight / 2;
|
||||
targetY = Math.max(margin, Math.min(targetY, windowHeight - ballHeight - margin));
|
||||
ballY.value = targetY;
|
||||
}
|
||||
|
||||
// 窗口resize监听器
|
||||
function handleResize() {
|
||||
updateBallPosition();
|
||||
}
|
||||
|
||||
// 处理悬浮球点击(区分点击和拖拽)
|
||||
function handleBallClick() {
|
||||
if (isDragging.value) return; // 拖动过程中不触发
|
||||
openDrawer();
|
||||
}
|
||||
|
||||
// 对话相关
|
||||
const conversations = ref<AiConversation[]>([]);
|
||||
const currentConversation = ref<AiConversation | null>(null);
|
||||
const hasMoreConversations = ref(false);
|
||||
@@ -447,9 +591,20 @@ function formatMarkdown(content: string) {
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
// 初始化悬浮球位置
|
||||
updateBallPosition();
|
||||
|
||||
// 监听窗口resize事件
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
loadAgentConfig();
|
||||
loadRecentConversations();
|
||||
});
|
||||
|
||||
// 清理监听器
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -459,11 +614,15 @@ onMounted(() => {
|
||||
right: 16px;
|
||||
bottom: 80px;
|
||||
z-index: 9999;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
.chat-ball {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
Reference in New Issue
Block a user