diff --git a/resources/assets/js/components/AIAssistant/index.vue b/resources/assets/js/components/AIAssistant/index.vue
index 215166607..68f66933f 100644
--- a/resources/assets/js/components/AIAssistant/index.vue
+++ b/resources/assets/js/components/AIAssistant/index.vue
@@ -1305,10 +1305,6 @@ export default {
.ai-assistant-content {
display: flex;
flex-direction: column;
- max-height: calc(var(--window-height) - var(--status-bar-height) - var(--navigation-bar-height) - 266px);
- @media (height <= 900px) {
- max-height: calc(var(--window-height) - var(--status-bar-height) - var(--navigation-bar-height) - 136px);
- }
.ai-assistant-welcome,
.ai-assistant-output {
@@ -1575,13 +1571,12 @@ export default {
.ai-assistant-chat {
position: fixed;
- right: 24px;
- bottom: 24px;
width: 460px;
- height: 80vh;
+ height: 600px;
min-width: 380px;
- max-width: 600px;
- max-height: 640px;
+ max-width: 800px;
+ min-height: 400px;
+ max-height: 900px;
background-color: #ffffff;
box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.12);
border-radius: 16px;
@@ -1589,6 +1584,72 @@ export default {
display: flex;
flex-direction: column;
+ // 调整大小控制点基础样式
+ .ai-assistant-resize-handle {
+ position: absolute;
+ z-index: 10;
+ }
+
+ // 四边控制点
+ .ai-assistant-resize-n {
+ top: 0;
+ left: 8px;
+ right: 8px;
+ height: 6px;
+ cursor: n-resize;
+ }
+ .ai-assistant-resize-s {
+ bottom: 0;
+ left: 8px;
+ right: 8px;
+ height: 6px;
+ cursor: s-resize;
+ }
+ .ai-assistant-resize-e {
+ top: 8px;
+ right: 0;
+ bottom: 8px;
+ width: 6px;
+ cursor: e-resize;
+ }
+ .ai-assistant-resize-w {
+ top: 8px;
+ left: 0;
+ bottom: 8px;
+ width: 6px;
+ cursor: w-resize;
+ }
+
+ // 四角控制点
+ .ai-assistant-resize-ne {
+ top: 0;
+ right: 0;
+ width: 12px;
+ height: 12px;
+ cursor: ne-resize;
+ }
+ .ai-assistant-resize-nw {
+ top: 0;
+ left: 0;
+ width: 12px;
+ height: 12px;
+ cursor: nw-resize;
+ }
+ .ai-assistant-resize-se {
+ bottom: 0;
+ right: 0;
+ width: 12px;
+ height: 12px;
+ cursor: se-resize;
+ }
+ .ai-assistant-resize-sw {
+ bottom: 0;
+ left: 0;
+ width: 12px;
+ height: 12px;
+ cursor: sw-resize;
+ }
+
.ai-assistant-close {
position: absolute;
top: 6px;
@@ -1631,14 +1692,10 @@ export default {
display: flex;
flex-direction: column;
align-items: center;
- justify-content: center;
-
- @media (max-height: 650px) {
- justify-content: normal;
- }
.ai-assistant-welcome-icon {
flex-shrink: 0;
+ margin-top: auto;
display: flex;
align-items: center;
justify-content: center;
@@ -1669,6 +1726,7 @@ export default {
gap: 12px;
max-width: 100%;
padding: 0 8px;
+ margin-bottom: auto;
}
.ai-assistant-prompt-card {
@@ -1735,6 +1793,12 @@ export default {
padding: 0 !important;
}
}
+ .ai-assistant-content {
+ max-height: calc(var(--window-height) - var(--status-bar-height) - var(--navigation-bar-height) - 266px);
+ @media (height <= 900px) {
+ max-height: calc(var(--window-height) - var(--status-bar-height) - var(--navigation-bar-height) - 136px);
+ }
+ }
}
body.dark-mode-reverse {
diff --git a/resources/assets/js/components/AIAssistant/modal.vue b/resources/assets/js/components/AIAssistant/modal.vue
index 3c245219b..f5b0a21c2 100644
--- a/resources/assets/js/components/AIAssistant/modal.vue
+++ b/resources/assets/js/components/AIAssistant/modal.vue
@@ -9,10 +9,19 @@
+ @mousedown.stop.prevent="onDragMouseDown">
+
+
+
+
+
+
+
+
+
@@ -64,11 +73,31 @@ export default {
dragging: false,
positionLoaded: false,
cacheKey: 'aiAssistant.chatPosition',
+ sizeCacheKey: 'aiAssistant.chatSize',
+ // 窗口尺寸(用于计算位置)
windowSize: {
width: 460,
- height: 640,
+ height: 600,
+ },
+ // 用户自定义尺寸
+ customSize: {
+ width: null,
+ height: null,
+ },
+ // 尺寸限制
+ minSize: {
+ width: 380,
+ height: 400,
+ },
+ maxSize: {
+ width: 800,
+ height: 900,
},
record: {},
+ // 调整大小相关
+ resizing: false,
+ resizeDirection: null,
+ resizeRecord: {},
};
},
@@ -83,11 +112,11 @@ export default {
},
clientWidth() {
- return this.windowWidth || document.documentElement.clientWidth;
+ return this.windowWidth;
},
clientHeight() {
- return this.windowHeight || document.documentElement.clientHeight;
+ return this.windowHeight;
},
// 计算实际的 left 值
@@ -112,10 +141,18 @@ export default {
opacity: 0,
};
}
- return {
+ const style = {
left: `${this.left}px`,
top: `${this.top}px`,
};
+ // 应用自定义尺寸
+ if (this.customSize.width) {
+ style.width = `${this.customSize.width}px`;
+ }
+ if (this.customSize.height) {
+ style.height = `${this.customSize.height}px`;
+ }
+ return style;
},
},
@@ -127,17 +164,23 @@ export default {
});
}
},
+ windowWidth() {
+ this.onViewportChange();
+ },
+ windowHeight() {
+ this.onViewportChange();
+ },
},
mounted() {
- this.loadPosition();
- window.addEventListener('resize', this.onResize);
+ this.loadSizeAndPosition();
},
beforeDestroy() {
- window.removeEventListener('resize', this.onResize);
- document.removeEventListener('mousemove', this.onMouseMove);
- document.removeEventListener('mouseup', this.onMouseUp);
+ document.removeEventListener('mousemove', this.onDragMouseMove);
+ document.removeEventListener('mouseup', this.onDragMouseUp);
+ document.removeEventListener('mousemove', this.onResizeMouseMove);
+ document.removeEventListener('mouseup', this.onResizeMouseUp);
document.removeEventListener('contextmenu', this.onContextMenu);
},
@@ -206,40 +249,40 @@ export default {
},
/**
- * 鼠标按下
+ * 拖动:鼠标按下
*/
- onMouseDown(e) {
+ onDragMouseDown(e) {
// 只响应鼠标左键
if (e.button !== 0) return;
this.updateWindowSize();
this.record = {
- time: Date.now(),
- startLeft: this.left,
- startTop: this.top,
offsetX: e.clientX - this.left,
offsetY: e.clientY - this.top,
};
this.dragging = true;
- document.addEventListener('mousemove', this.onMouseMove);
- document.addEventListener('mouseup', this.onMouseUp);
+ document.addEventListener('mousemove', this.onDragMouseMove);
+ document.addEventListener('mouseup', this.onDragMouseUp);
document.addEventListener('contextmenu', this.onContextMenu);
},
/**
- * 右键菜单弹出时取消拖动
+ * 右键菜单弹出时取消拖动/调整大小
*/
onContextMenu() {
if (this.dragging) {
- this.onMouseUp();
+ this.onDragMouseUp();
+ }
+ if (this.resizing) {
+ this.onResizeMouseUp();
}
},
/**
- * 鼠标移动
+ * 拖动:鼠标移动
*/
- onMouseMove(e) {
+ onDragMouseMove(e) {
if (!this.dragging) return;
const minMargin = 12;
@@ -254,17 +297,156 @@ export default {
},
/**
- * 鼠标松开
+ * 拖动:鼠标松开
*/
- onMouseUp() {
- document.removeEventListener('mousemove', this.onMouseMove);
- document.removeEventListener('mouseup', this.onMouseUp);
+ onDragMouseUp() {
+ document.removeEventListener('mousemove', this.onDragMouseMove);
+ document.removeEventListener('mouseup', this.onDragMouseUp);
document.removeEventListener('contextmenu', this.onContextMenu);
this.savePosition();
this.dragging = false;
},
+ /**
+ * 调整大小:鼠标按下
+ */
+ onResizeMouseDown(e, direction) {
+ if (e.button !== 0) return;
+
+ this.updateWindowSize();
+ this.resizeDirection = direction;
+ this.resizeRecord = {
+ startX: e.clientX,
+ startY: e.clientY,
+ startWidth: this.windowSize.width,
+ startHeight: this.windowSize.height,
+ startLeft: this.left,
+ startTop: this.top,
+ };
+ this.resizing = true;
+
+ document.addEventListener('mousemove', this.onResizeMouseMove);
+ document.addEventListener('mouseup', this.onResizeMouseUp);
+ document.addEventListener('contextmenu', this.onContextMenu);
+ },
+
+ /**
+ * 调整大小:鼠标移动
+ */
+ onResizeMouseMove(e) {
+ if (!this.resizing) return;
+
+ const dir = this.resizeDirection;
+ const deltaX = e.clientX - this.resizeRecord.startX;
+ const deltaY = e.clientY - this.resizeRecord.startY;
+
+ let newWidth = this.resizeRecord.startWidth;
+ let newHeight = this.resizeRecord.startHeight;
+ let newLeft = this.resizeRecord.startLeft;
+ let newTop = this.resizeRecord.startTop;
+
+ // 根据方向计算新尺寸
+ if (dir.includes('e')) {
+ newWidth = this.resizeRecord.startWidth + deltaX;
+ }
+ if (dir.includes('w')) {
+ newWidth = this.resizeRecord.startWidth - deltaX;
+ newLeft = this.resizeRecord.startLeft + deltaX;
+ }
+ if (dir.includes('s')) {
+ newHeight = this.resizeRecord.startHeight + deltaY;
+ }
+ if (dir.includes('n')) {
+ newHeight = this.resizeRecord.startHeight - deltaY;
+ newTop = this.resizeRecord.startTop + deltaY;
+ }
+
+ // 限制最小/最大尺寸
+ const minMargin = 12;
+ const maxWidth = Math.min(this.maxSize.width, this.clientWidth - minMargin * 2);
+ const maxHeight = Math.min(this.maxSize.height, this.clientHeight - minMargin * 2);
+
+ newWidth = Math.max(this.minSize.width, Math.min(newWidth, maxWidth));
+ newHeight = Math.max(this.minSize.height, Math.min(newHeight, maxHeight));
+
+ // 如果是从左边或上边调整,需要修正位置
+ if (dir.includes('w')) {
+ const widthDiff = newWidth - this.resizeRecord.startWidth;
+ newLeft = this.resizeRecord.startLeft - widthDiff;
+ }
+ if (dir.includes('n')) {
+ const heightDiff = newHeight - this.resizeRecord.startHeight;
+ newTop = this.resizeRecord.startTop - heightDiff;
+ }
+
+ // 边界限制位置
+ newLeft = Math.max(minMargin, Math.min(newLeft, this.clientWidth - newWidth - minMargin));
+ newTop = Math.max(minMargin, Math.min(newTop, this.clientHeight - newHeight - minMargin));
+
+ // 更新尺寸
+ this.customSize.width = newWidth;
+ this.customSize.height = newHeight;
+ this.windowSize.width = newWidth;
+ this.windowSize.height = newHeight;
+
+ // 更新位置
+ this.updatePositionFromCoords(newLeft, newTop);
+ },
+
+ /**
+ * 调整大小:鼠标松开
+ */
+ onResizeMouseUp() {
+ document.removeEventListener('mousemove', this.onResizeMouseMove);
+ document.removeEventListener('mouseup', this.onResizeMouseUp);
+ document.removeEventListener('contextmenu', this.onContextMenu);
+
+ this.saveSize();
+ this.savePosition();
+ this.resizing = false;
+ this.resizeDirection = null;
+ },
+
+ /**
+ * 先加载尺寸,再加载位置(确保位置计算时使用正确的尺寸)
+ */
+ async loadSizeAndPosition() {
+ await this.loadSize();
+ await this.loadPosition();
+ },
+
+ /**
+ * 加载保存的尺寸
+ */
+ async loadSize() {
+ try {
+ const saved = await $A.IDBString(this.sizeCacheKey);
+ if (saved) {
+ const size = JSON.parse(saved);
+ if (size && typeof size.width === 'number' && typeof size.height === 'number') {
+ this.customSize = {
+ width: Math.max(this.minSize.width, Math.min(size.width, this.maxSize.width)),
+ height: Math.max(this.minSize.height, Math.min(size.height, this.maxSize.height)),
+ };
+ this.windowSize.width = this.customSize.width;
+ this.windowSize.height = this.customSize.height;
+ }
+ }
+ } catch (e) {
+ // ignore
+ }
+ },
+
+ /**
+ * 保存尺寸
+ */
+ saveSize() {
+ if (this.customSize.width && this.customSize.height) {
+ $A.IDBSave(this.sizeCacheKey, JSON.stringify(this.customSize));
+ }
+ },
+
/**
* 检查边界(仅在加载和窗口变化时调用)
*/
@@ -278,13 +460,29 @@ export default {
},
/**
- * 窗口大小改变
+ * 视口尺寸变化
*/
- onResize() {
- this.$nextTick(() => {
- this.updateWindowSize();
- this.checkBounds();
- });
+ onViewportChange() {
+ this.constrainSizeToScreen();
+ this.checkBounds();
+ },
+
+ /**
+ * 限制尺寸不超出屏幕
+ */
+ constrainSizeToScreen() {
+ const minMargin = 12;
+ const maxWidth = this.clientWidth - minMargin * 2;
+ const maxHeight = this.clientHeight - minMargin * 2;
+
+ if (this.customSize.width && this.customSize.width > maxWidth) {
+ this.customSize.width = Math.max(this.minSize.width, maxWidth);
+ this.windowSize.width = this.customSize.width;
+ }
+ if (this.customSize.height && this.customSize.height > maxHeight) {
+ this.customSize.height = Math.max(this.minSize.height, maxHeight);
+ this.windowSize.height = this.customSize.height;
+ }
},
onClose() {