From d7087b250ccafaf4adc0b4b5c6f9db46a235a61b Mon Sep 17 00:00:00 2001 From: ShineKOT <1917095344@qq.com> Date: Fri, 7 Mar 2025 15:22:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=20=E6=96=B0=E5=A2=9E=E6=8B=96=E6=8B=BD?= =?UTF-8?q?=E8=BE=B9=E7=95=8C=EF=BC=8CAI=E5=AF=B9=E8=AF=9D=E6=A1=86?= =?UTF-8?q?=E5=90=B8=E9=99=84=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?AI=E5=AF=B9=E8=AF=9D=E6=A1=86=E8=87=AA=E9=80=82=E5=BA=94?= =?UTF-8?q?=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat-container/chat-container.scss | 1 + .../chat-container/chat-container.tsx | 174 +++++++++++++----- 2 files changed, 131 insertions(+), 44 deletions(-) diff --git a/src/components/chat-container/chat-container.scss b/src/components/chat-container/chat-container.scss index 7fab7c4..427fdd0 100644 --- a/src/components/chat-container/chat-container.scss +++ b/src/components/chat-container/chat-container.scss @@ -93,6 +93,7 @@ $ai-chat: ( justify-content: space-between; height: 60px; cursor: move; + padding: 4px; border-bottom: 1px solid #{getCssVar('ai-chat', 'border-color')}; } diff --git a/src/components/chat-container/chat-container.tsx b/src/components/chat-container/chat-container.tsx index 073e1a5..b7112e2 100644 --- a/src/components/chat-container/chat-container.tsx +++ b/src/components/chat-container/chat-container.tsx @@ -166,17 +166,17 @@ export class ChatContainer extends Component< minimizeRef = createRef(); data = { - x: window.innerWidth - 600, y: 0, - width: 600, - height: window.innerHeight, + height: 1, + width: 600 / window.innerWidth, + x: (window.innerWidth - 600) / window.innerWidth, minWidth: 500, minHeight: 300, }; minimizeData = { - x: window.innerWidth - 86, - y: window.innerHeight - 86, + x: (window.innerWidth - 86) / window.innerWidth, + y: (window.innerHeight - 86) / window.innerHeight, }; /** @@ -208,10 +208,10 @@ export class ChatContainer extends Component< calcWindowStyle() { return { - left: `${(this.data.x / window.innerWidth) * 100}%`, - top: `${(this.data.y / window.innerHeight) * 100}%`, - width: `${(this.data.width / window.innerWidth) * 100}%`, - height: `${(this.data.height / window.innerHeight) * 100}%`, + left: `${this.data.x * 100}%`, + top: `${this.data.y * 100}%`, + width: `${this.data.width * 100}%`, + height: `${this.data.height * 100}%`, minWidth: `${this.data.minWidth}px`, minHeight: `${this.data.minHeight}px`, 'z-index': this.props.containerOptions?.zIndex?.toString() || '10', @@ -220,38 +220,76 @@ export class ChatContainer extends Component< calcMinimizeStyle() { return { - left: `${(this.minimizeData.x / window.innerWidth) * 100}%`, - top: `${(this.minimizeData.y / window.innerHeight) * 100}%`, + left: `${this.minimizeData.x * 100}%`, + top: `${this.minimizeData.y * 100}%`, 'z-index': '99999', }; } - setStyle() { + setStyle(): void { Object.assign(this.containerRef.current!.style, this.calcWindowStyle()); Object.assign(this.minimizeRef.current!.style, this.calcMinimizeStyle()); } - componentDidMount(): void { - const minimizeCache = localStorage.getItem( - AIChatConst.MINIMIZE_STYLY_CHCHE, - ); - if (minimizeCache) { - this.minimizeData = JSON.parse(minimizeCache); - } - const cache = localStorage.getItem(AIChatConst.STYLE_CACHE); - if (cache) { - this.data = JSON.parse(cache); - // 保持在窗口内部 - if (this.data.x > window.innerWidth) { - this.data.x = window.innerWidth - 100; - } - if (this.data.y > window.innerHeight) { - this.data.y = window.innerHeight - 100; - } - } + /** + * 检查是否在窗口内部 + * + * @param {{ x: number; y: number }} data + * @return {*} {boolean} + * @memberof ChatContainer + */ + isWithinBounds(data: { x: number; y: number }): boolean { + return data.x >= 0 && data.x <= 1 && data.y >= 0 && data.y <= 1; + } + + /** + * 吸附边缘 + * - 靠近窗口边缘(20px)自动吸附 + * @memberof ChatContainer + */ + snapToEdge(): void { + const snapWidth = 20 / window.innerWidth; + const snapHeight = 20 / window.innerHeight; + if (this.data.x < snapWidth) this.data.x = 0; + if (this.data.x + this.data.width > 1 - snapWidth) + this.data.x = 1 - this.data.width; + if (this.data.y < snapHeight) this.data.y = 0; + if (this.data.y + this.data.height > 1 - snapHeight) + this.data.y = 1 - this.data.height; this.setStyle(); - const state = this.data; - // 注册最小化拖拽 + } + + /** + * 拖拽限制(边界问题) + * + * @param {number} left + * @param {number} top + * @param {number} width + * @param {number} height + * @memberof ChatContainer + */ + limitDraggable( + left: number, + top: number, + width: number, + height: number, + ): { + x: number; + y: number; + } { + const offsetX = left / window.innerWidth; + const offsetY = top / window.innerHeight; + const x = Math.max(0, Math.min(offsetX, 1 - width)); + const y = Math.max(0, Math.min(offsetY, 1 - height)); + return { x, y }; + } + + /** + * 注册最小化拖拽 + * + * @memberof ChatContainer + */ + registerDragMinmize(): void { this.minimizeRef.current!.onmousedown = (e: MouseEvent): void => { // 禁止选择文本,避免拖动时出现选择效果 document.body.style.userSelect = 'none'; @@ -259,8 +297,15 @@ export class ChatContainer extends Component< const offsetY = e.clientY - this.minimizeRef.current!.offsetTop; const start = Date.now(); const onMouseMove = (evt: MouseEvent): void => { - this.minimizeData.x = evt.clientX - offsetX; - this.minimizeData.y = evt.clientY - offsetY; + const width = 56 / window.innerWidth; + const height = 56 / window.innerHeight; + const { x, y } = this.limitDraggable( + evt.clientX - offsetX, + evt.clientY - offsetY, + width, + height, + ); + Object.assign(this.minimizeData, { x, y }); this.setStyle(); }; const onMouseUp = (): void => { @@ -276,16 +321,29 @@ export class ChatContainer extends Component< document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); }; - // 注册窗口拖拽 + } + + /** + * 注册对话框拖拽 + * + * @memberof ChatContainer + */ + registerDragDialog(): void { this.dragHandle.current!.onmousedown = (e: MouseEvent): void => { - if (this.disabled || this.state.isFullScreen) return; + if (this.state.isFullScreen) return; // 禁止选择文本,避免拖动时出现选择效果 document.body.style.userSelect = 'none'; const offsetX = e.clientX - this.containerRef.current!.offsetLeft; const offsetY = e.clientY - this.containerRef.current!.offsetTop; const onMouseMove = (evt: MouseEvent): void => { - state.x = evt.clientX - offsetX; - state.y = evt.clientY - offsetY; + if (this.disabled) return; + const { x, y } = this.limitDraggable( + evt.clientX - offsetX, + evt.clientY - offsetY, + this.data.width, + this.data.height, + ); + Object.assign(this.data, { x, y }); this.setStyle(); }; @@ -294,11 +352,21 @@ export class ChatContainer extends Component< document.body.style.userSelect = ''; document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); + if (this.disabled) return; + this.snapToEdge(); }; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); }; + } + + /** + * 注册对话框边界拖拽 + * + * @memberof ChatContainer + */ + registerDragDialogBorder(): void { // 注册窗口大小变更 interact(this.containerRef.current!).resizable({ // 可拖拽的边缘 @@ -314,18 +382,17 @@ export class ChatContainer extends Component< interact.modifiers.restrictEdges({ outer: document.body }), // 缩放最小宽度 interact.modifiers.restrictSize({ - min: { width: state.minWidth, height: state.minHeight }, + min: { width: this.data.minWidth, height: this.data.minHeight }, }), ], inertia: true, listeners: { move: event => { if (this.state.isFullScreen) return; - state.x = event.rect.left; - state.y = event.rect.top; - // 更新宽高 - state.width = event.rect.width; - state.height = event.rect.height; + this.data.x = event.rect.left / window.innerWidth; + this.data.y = event.rect.top / window.innerHeight; + this.data.width = event.rect.width / window.innerWidth; + this.data.height = event.rect.height / window.innerHeight; this.setStyle(); }, start: () => { @@ -342,6 +409,25 @@ export class ChatContainer extends Component< }); } + componentDidMount(): void { + const minimizeCache = localStorage.getItem( + AIChatConst.MINIMIZE_STYLY_CHCHE, + ); + if (minimizeCache) { + const data = JSON.parse(minimizeCache); + if (this.isWithinBounds(data)) this.minimizeData = data; + } + const cache = localStorage.getItem(AIChatConst.STYLE_CACHE); + if (cache) { + const data = JSON.parse(cache); + if (this.isWithinBounds(data)) this.data = data; + } + this.setStyle(); + this.registerDragDialog(); + this.registerDragDialogBorder(); + this.registerDragMinmize(); + } + componentWillUnmount(): void { localStorage.setItem(AIChatConst.STYLE_CACHE, JSON.stringify(this.data)); localStorage.setItem( -- Gitee