mirror of
https://codeup.aliyun.com/67762337eccfc218f6110e0e/vue/learning-system-portal.git
synced 2025-12-09 02:46:44 +08:00
feat(portal/case): 增强AI对话窗口交互功能
-为sendMessage组件添加textarea输入框,支持多行输入 - 为AI对话窗口添加拖拽和调整大小功能 - 在最小化窗口中添加关闭按钮- 优化窗口样式和布局,提升用户体验 - 添加拖拽手柄和窗口控制按钮 - 实现窗口位置和大小的动态调整 - 引入open.png图标用于最小化窗口操作
This commit is contained in:
@@ -14,11 +14,14 @@
|
||||
:fullscreen="false"
|
||||
top="10vh"
|
||||
v-show="windowState === 'maximized'"
|
||||
v-resizeable
|
||||
v-draggable
|
||||
>
|
||||
<!-- 标题 -->
|
||||
<div slot="title" class="dialog-title">
|
||||
<span>案例专家</span>
|
||||
<el-button
|
||||
style="color:#96999f"
|
||||
type="text"
|
||||
class="window-control-btn"
|
||||
@click="minimizeWindow"
|
||||
@@ -75,13 +78,24 @@
|
||||
>
|
||||
<div class="minimized-content">
|
||||
<span class="window-title">案例专家</span>
|
||||
<el-button
|
||||
type="text"
|
||||
class="window-control-btn"
|
||||
@click.stop="onMinimizedWindowClick"
|
||||
>
|
||||
<i class="el-icon-full-screen"></i>
|
||||
</el-button>
|
||||
<div style="display: flex;align-items: center">
|
||||
<el-button
|
||||
type="text"
|
||||
class="window-control-btn"
|
||||
@click.stop="onMinimizedWindowClick"
|
||||
>
|
||||
<img :src="openImg" alt="" style="width: 17px">
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
style="margin-left: 1px;color:#96999f"
|
||||
type="text"
|
||||
class="window-control-btn"
|
||||
@click.stop="closeMinimizedWindow"
|
||||
>
|
||||
<i class="el-icon-close"></i>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="minimized-message">
|
||||
<div v-if="messageList.length <= 1 && messageList[0].isBot">
|
||||
@@ -99,7 +113,7 @@
|
||||
import { mapState } from 'vuex'
|
||||
import messages from './components/messages.vue'
|
||||
import sendMessage from './components/sendMessage.vue'
|
||||
|
||||
import openImg from './components/open.png'
|
||||
export default {
|
||||
name: 'CaseExpertDialog',
|
||||
props: {
|
||||
@@ -112,6 +126,281 @@ export default {
|
||||
messages,
|
||||
sendMessage
|
||||
},
|
||||
directives: {
|
||||
draggable: {
|
||||
bind(el, binding, vnode) {
|
||||
vnode.context.$nextTick(() => {
|
||||
const dialogEl = el.querySelector('.el-dialog');
|
||||
if (!dialogEl) return;
|
||||
|
||||
const headerEl = dialogEl.querySelector('.dialog-title');
|
||||
if (!headerEl) return;
|
||||
|
||||
// 设置初始样式
|
||||
dialogEl.style.position = 'fixed';
|
||||
dialogEl.style.top = '100px';
|
||||
dialogEl.style.left = (window.innerWidth - dialogEl.offsetWidth) / 2 + 'px';
|
||||
dialogEl.style.margin = '0';
|
||||
|
||||
let isDragging = false;
|
||||
let startX = 0;
|
||||
let startY = 0;
|
||||
let startLeft = 0;
|
||||
let startTop = 0;
|
||||
|
||||
const startDrag = (event) => {
|
||||
// 只有在标题栏上按下鼠标才开始拖动
|
||||
if (event.target.closest('.resize-handle')) {
|
||||
return; // 如果点击的是resize-handle,则不触发拖动
|
||||
}
|
||||
|
||||
isDragging = true;
|
||||
startX = event.clientX;
|
||||
startY = event.clientY;
|
||||
startLeft = parseInt(dialogEl.style.left) || dialogEl.offsetLeft;
|
||||
startTop = parseInt(dialogEl.style.top) || dialogEl.offsetTop;
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
// 添加全局事件监听
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('mouseup', stopDrag);
|
||||
};
|
||||
|
||||
const handleMouseMove = (event) => {
|
||||
if (!isDragging) return;
|
||||
|
||||
const deltaX = event.clientX - startX;
|
||||
const deltaY = event.clientY - startY;
|
||||
|
||||
dialogEl.style.left = (startLeft + deltaX) + 'px';
|
||||
dialogEl.style.top = (startTop + deltaY) + 'px';
|
||||
};
|
||||
|
||||
const stopDrag = () => {
|
||||
isDragging = false;
|
||||
|
||||
// 移除全局事件监听
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseup', stopDrag);
|
||||
};
|
||||
|
||||
// 为标题栏绑定拖动事件
|
||||
headerEl.addEventListener('mousedown', startDrag);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
resizeable: {
|
||||
bind(el, binding, vnode) {
|
||||
// 确保元素已插入DOM
|
||||
vnode.context.$nextTick(() => {
|
||||
const dialogEl = el.querySelector('.el-dialog');
|
||||
if (!dialogEl) return;
|
||||
|
||||
// 设置初始样式
|
||||
dialogEl.style.position = 'fixed';
|
||||
dialogEl.style.top = '100px';
|
||||
dialogEl.style.left = (window.innerWidth - dialogEl.offsetWidth) / 2 + 'px';
|
||||
|
||||
// 创建拖拽手柄
|
||||
const createHandle = (direction) => {
|
||||
const handle = document.createElement('div');
|
||||
handle.className = `resize-handle ${direction}`;
|
||||
handle.style.position = 'absolute';
|
||||
handle.style.zIndex = '10';
|
||||
|
||||
switch (direction) {
|
||||
case 'left':
|
||||
case 'right':
|
||||
handle.style.width = '6px';
|
||||
handle.style.height = '100%';
|
||||
handle.style.top = '0';
|
||||
handle.style.cursor = 'ew-resize';
|
||||
break;
|
||||
case 'top':
|
||||
case 'bottom':
|
||||
handle.style.width = '100%';
|
||||
handle.style.height = '6px';
|
||||
handle.style.left = '0';
|
||||
handle.style.cursor = 'ns-resize';
|
||||
break;
|
||||
case 'top-left':
|
||||
case 'top-right':
|
||||
case 'bottom-left':
|
||||
case 'bottom-right':
|
||||
handle.style.width = '10px';
|
||||
handle.style.height = '10px';
|
||||
handle.style.zIndex = '20';
|
||||
break;
|
||||
}
|
||||
|
||||
switch (direction) {
|
||||
case 'left':
|
||||
handle.style.left = '0';
|
||||
break;
|
||||
case 'right':
|
||||
handle.style.right = '0';
|
||||
break;
|
||||
case 'top':
|
||||
handle.style.top = '0';
|
||||
break;
|
||||
case 'bottom':
|
||||
handle.style.bottom = '0';
|
||||
break;
|
||||
case 'top-left':
|
||||
handle.style.top = '0';
|
||||
handle.style.left = '0';
|
||||
handle.style.cursor = 'nw-resize';
|
||||
break;
|
||||
case 'top-right':
|
||||
handle.style.top = '0';
|
||||
handle.style.right = '0';
|
||||
handle.style.cursor = 'ne-resize';
|
||||
break;
|
||||
case 'bottom-left':
|
||||
handle.style.bottom = '0';
|
||||
handle.style.left = '0';
|
||||
handle.style.cursor = 'sw-resize';
|
||||
break;
|
||||
case 'bottom-right':
|
||||
handle.style.bottom = '0';
|
||||
handle.style.right = '0';
|
||||
handle.style.cursor = 'se-resize';
|
||||
break;
|
||||
}
|
||||
|
||||
// 防止拖拽手柄的事件冒泡到标题栏
|
||||
handle.addEventListener('mousedown', (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
dialogEl.appendChild(handle);
|
||||
return handle;
|
||||
};
|
||||
|
||||
// 创建8个拖拽手柄
|
||||
const handles = {
|
||||
left: createHandle('left'),
|
||||
right: createHandle('right'),
|
||||
top: createHandle('top'),
|
||||
bottom: createHandle('bottom'),
|
||||
topLeft: createHandle('top-left'),
|
||||
topRight: createHandle('top-right'),
|
||||
bottomLeft: createHandle('bottom-left'),
|
||||
bottomRight: createHandle('bottom-right')
|
||||
};
|
||||
|
||||
// 添加拖拽事件处理
|
||||
let isResizing = false;
|
||||
let resizeDirection = '';
|
||||
let startX = 0;
|
||||
let startY = 0;
|
||||
let startWidth = 0;
|
||||
let startHeight = 0;
|
||||
let startLeft = 0;
|
||||
let startTop = 0;
|
||||
|
||||
const startResize = (direction, event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
isResizing = true;
|
||||
resizeDirection = direction;
|
||||
|
||||
startX = event.clientX;
|
||||
startY = event.clientY;
|
||||
startWidth = dialogEl.offsetWidth;
|
||||
startHeight = dialogEl.offsetHeight;
|
||||
|
||||
// 统一使用计算后的样式值
|
||||
startLeft = parseInt(dialogEl.style.left) || 0;
|
||||
startTop = parseInt(dialogEl.style.top) || 0;
|
||||
|
||||
// 添加全局事件监听
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('mouseup', stopResize);
|
||||
};
|
||||
|
||||
const handleMouseMove = (event) => {
|
||||
if (!isResizing) return;
|
||||
|
||||
const deltaX = event.clientX - startX;
|
||||
const deltaY = event.clientY - startY;
|
||||
|
||||
switch (resizeDirection) {
|
||||
case 'right':
|
||||
dialogEl.style.width = Math.max(400, startWidth + deltaX) + 'px';
|
||||
break;
|
||||
case 'left':
|
||||
const newWidth = Math.max(400, startWidth - deltaX);
|
||||
dialogEl.style.width = newWidth + 'px';
|
||||
dialogEl.style.left = (startLeft + startWidth - newWidth) + 'px';
|
||||
break;
|
||||
case 'bottom':
|
||||
dialogEl.style.height = Math.max(600, startHeight + deltaY) + 'px';
|
||||
break;
|
||||
case 'top':
|
||||
// 当窗口高度达到最小值时,不再调整高度和位置
|
||||
if (startHeight - deltaY >= 600) {
|
||||
dialogEl.style.height = (startHeight - deltaY) + 'px';
|
||||
dialogEl.style.top = (startTop + deltaY) + 'px';
|
||||
}
|
||||
break;
|
||||
case 'bottom-right':
|
||||
dialogEl.style.width = Math.max(400, startWidth + deltaX) + 'px';
|
||||
dialogEl.style.height = Math.max(600, startHeight + deltaY) + 'px';
|
||||
break;
|
||||
case 'bottom-left':
|
||||
const newWidthBL = Math.max(400, startWidth - deltaX);
|
||||
dialogEl.style.width = newWidthBL + 'px';
|
||||
dialogEl.style.left = (startLeft + startWidth - newWidthBL) + 'px';
|
||||
dialogEl.style.height = Math.max(600, startHeight + deltaY) + 'px';
|
||||
break;
|
||||
case 'top-right':
|
||||
// 当窗口高度达到最小值时,不再调整高度和位置
|
||||
if (startHeight - deltaY >= 600) {
|
||||
dialogEl.style.height = (startHeight - deltaY) + 'px';
|
||||
dialogEl.style.top = (startTop + deltaY) + 'px';
|
||||
}
|
||||
dialogEl.style.width = Math.max(400, startWidth + deltaX) + 'px';
|
||||
break;
|
||||
case 'top-left':
|
||||
// 当窗口高度达到最小值时,不再调整高度和位置
|
||||
if (startHeight - deltaY >= 600) {
|
||||
dialogEl.style.height = (startHeight - deltaY) + 'px';
|
||||
dialogEl.style.top = (startTop + deltaY) + 'px';
|
||||
}
|
||||
const newWidthTL = Math.max(400, startWidth - deltaX);
|
||||
dialogEl.style.width = newWidthTL + 'px';
|
||||
dialogEl.style.left = (startLeft + startWidth - newWidthTL) + 'px';
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const stopResize = () => {
|
||||
isResizing = false;
|
||||
resizeDirection = '';
|
||||
|
||||
// 移除全局事件监听
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseup', stopResize);
|
||||
};
|
||||
|
||||
// 为每个手柄绑定事件
|
||||
handles.left.addEventListener('mousedown', (e) => startResize('left', e));
|
||||
handles.right.addEventListener('mousedown', (e) => startResize('right', e));
|
||||
handles.top.addEventListener('mousedown', (e) => startResize('top', e));
|
||||
handles.bottom.addEventListener('mousedown', (e) => startResize('bottom', e));
|
||||
handles.topLeft.addEventListener('mousedown', (e) => startResize('top-left', e));
|
||||
handles.topRight.addEventListener('mousedown', (e) => startResize('top-right', e));
|
||||
handles.bottomLeft.addEventListener('mousedown', (e) => startResize('bottom-left', e));
|
||||
handles.bottomRight.addEventListener('mousedown', (e) => startResize('bottom-right', e));
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('app', ['showAICallMinimized']),
|
||||
showMinimizedWindow() {
|
||||
@@ -121,6 +410,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
openImg,
|
||||
AIContent: '',
|
||||
isLoading: false,
|
||||
windowState: 'maximized', // 'maximized' 或 'minimized'
|
||||
@@ -153,6 +443,12 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// / 关闭最小化窗口
|
||||
closeMinimizedWindow() {
|
||||
this.$store.commit('app/SET_SHOW_AI_CALL_MINIMIZED', false);
|
||||
this.$store.commit('app/SET_SHOW_AI_CALL', false);
|
||||
this.windowState = 'maximized';
|
||||
},
|
||||
getMinWidow(vis){
|
||||
// this.showAICallMinimized = vis
|
||||
this.windowState = 'minimized';
|
||||
@@ -165,6 +461,7 @@ export default {
|
||||
|
||||
minimizeWindow() {
|
||||
this.windowState = 'minimized';
|
||||
this.$store.commit('app/SET_SHOW_AI_CALL_MINIMIZED', true);
|
||||
},
|
||||
|
||||
maximizeWindow() {
|
||||
@@ -258,11 +555,14 @@ export default {
|
||||
background-size: cover;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
//background-color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
::v-deep .el-dialog__body{
|
||||
padding: 10px;
|
||||
flex:1;
|
||||
//font-size: 12px;
|
||||
*{
|
||||
font-size:unset ;
|
||||
@@ -279,6 +579,7 @@ export default {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
padding-right: 20px;
|
||||
cursor: move; /* 添加拖动样式 */
|
||||
|
||||
.icon {
|
||||
width: 24px;
|
||||
@@ -310,7 +611,8 @@ export default {
|
||||
padding: 20px;
|
||||
background-color: transparent;
|
||||
border-radius: 8px;
|
||||
height: 550px;
|
||||
min-height: 500px;
|
||||
height:100%;
|
||||
position: relative;
|
||||
//margin-bottom: 20px;
|
||||
display: flex;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<div id="case-list-content">
|
||||
<div style="margin-bottom:30px" class="case-banner">
|
||||
<portal-header current="case" textColor="#fff" :goSearch="2"></portal-header>
|
||||
<portal-header current="case" textColor="#fff" :goSearch="2">
|
||||
|
||||
</portal-header>
|
||||
</div>
|
||||
<div class="">
|
||||
<div class="xcontent2">
|
||||
|
||||
BIN
src/views/portal/case/components/open.png
Normal file
BIN
src/views/portal/case/components/open.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 283 B |
@@ -2,10 +2,13 @@
|
||||
<div class="input-area">
|
||||
<el-input
|
||||
v-model="inputContent"
|
||||
type="textarea"
|
||||
class="input-placeholder"
|
||||
placeholder="有问题,尽管问"
|
||||
@keyup.enter.native="handleSend"
|
||||
:disabled="disabled"
|
||||
:autosize="{ minRows: 2, maxRows: 4}"
|
||||
resize="none"
|
||||
></el-input>
|
||||
<div class="action-buttons">
|
||||
<el-button type="primary" size="small" class="start-btn" @click="handleNewConversation">
|
||||
|
||||
Reference in New Issue
Block a user