样式修改
This commit is contained in:
@@ -1,8 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="hasAgent">
|
<div v-if="hasAgent">
|
||||||
<!-- 悬浮球 -->
|
<!-- 悬浮球 -->
|
||||||
<div class="ball-container">
|
<div
|
||||||
<div class="chat-ball" @click="openDrawer">
|
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" />
|
<img src="@/assets/imgs/chat-ball.svg" alt="AI助手" class="ball-icon" />
|
||||||
<div v-if="unreadCount > 0" class="unread-badge">{{ unreadCount }}</div>
|
<div v-if="unreadCount > 0" class="unread-badge">{{ unreadCount }}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -134,7 +140,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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 { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
import { Close, Paperclip, VideoPause, Promotion } from '@element-plus/icons-vue';
|
import { Close, Paperclip, VideoPause, Promotion } from '@element-plus/icons-vue';
|
||||||
import { chatApi, chatHistoryApi, aiAgentConfigApi, fileUploadApi } from '@/apis/ai';
|
import { chatApi, chatHistoryApi, aiAgentConfigApi, fileUploadApi } from '@/apis/ai';
|
||||||
@@ -160,15 +166,27 @@ const agentAvatarUrl = ref<string>('');
|
|||||||
|
|
||||||
// ===== 悬浮球相关 =====
|
// ===== 悬浮球相关 =====
|
||||||
const ballRef = ref<HTMLElement | null>(null);
|
const ballRef = ref<HTMLElement | null>(null);
|
||||||
const ballX = ref(0);
|
const ballX = ref(0); // 实际位置(像素)
|
||||||
const ballY = ref(0);
|
const ballY = ref(0);
|
||||||
const unreadCount = 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(() => ({
|
const ballStyle = computed(() => ({
|
||||||
right: '20px',
|
left: `${ballX.value}px`,
|
||||||
bottom: '100px',
|
top: `${ballY.value}px`,
|
||||||
position: 'fixed'
|
position: 'fixed' as const
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 打开抽屉
|
// 打开抽屉
|
||||||
@@ -176,7 +194,133 @@ function openDrawer() {
|
|||||||
drawerVisible.value = true;
|
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 conversations = ref<AiConversation[]>([]);
|
||||||
const currentConversation = ref<AiConversation | null>(null);
|
const currentConversation = ref<AiConversation | null>(null);
|
||||||
const hasMoreConversations = ref(false);
|
const hasMoreConversations = ref(false);
|
||||||
@@ -447,9 +591,20 @@ function formatMarkdown(content: string) {
|
|||||||
|
|
||||||
// 生命周期
|
// 生命周期
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
// 初始化悬浮球位置
|
||||||
|
updateBallPosition();
|
||||||
|
|
||||||
|
// 监听窗口resize事件
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
|
||||||
loadAgentConfig();
|
loadAgentConfig();
|
||||||
loadRecentConversations();
|
loadRecentConversations();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 清理监听器
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -459,11 +614,15 @@ onMounted(() => {
|
|||||||
right: 16px;
|
right: 16px;
|
||||||
bottom: 80px;
|
bottom: 80px;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-ball {
|
.chat-ball {
|
||||||
width: 56px;
|
width: 40px;
|
||||||
height: 56px;
|
height: 40px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: transform 0.3s ease;
|
transition: transform 0.3s ease;
|
||||||
|
|||||||
Reference in New Issue
Block a user