Просмотр исходного кода

查询助手呼出按钮可以拖动并记住位置

MaiXinRong 23 часов назад
Родитель
Сommit
d5184a907e
2 измененных файлов с 99 добавлено и 2 удалено
  1. 7 1
      app/public/css/main.css
  2. 92 1
      app/public/js/assistant.js

+ 7 - 1
app/public/css/main.css

@@ -2929,13 +2929,19 @@ button.gy-stat:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
   color: #fff;
   background: #3f8fe8;
   box-shadow: 0 6px 20px rgba(39, 126, 220, .3);
-  cursor: pointer;
+  cursor: grab;
+  touch-action: none;
+  user-select: none;
 }
 
 .global-assistant-entry:hover {
   background: #2e7fd6;
 }
 
+.global-assistant-entry.dragging {
+  cursor: grabbing;
+}
+
 .global-assistant-panel {
   display: none;
   flex-direction: column;

+ 92 - 1
app/public/js/assistant.js

@@ -10,19 +10,78 @@
         const suggestions = root.find('.global-assistant-suggestions');
         const input = root.find('.global-assistant-input');
         const sendButton = root.find('.global-assistant-send');
+        const entry = root.find('.global-assistant-entry');
         const projectId = root.data('project-id');
         const storageKey = 'global-assistant-history-v2-' + projectId + '-' + userID;
+        const entryPositionKey = 'global-assistant-entry-position-' + userID;
         let history = loadHistory();
         let conversationId = getConversationId();
         let sending = false;
+        let entryPosition = loadEntryPosition();
+        let entryDrag = null;
+        let suppressEntryClick = false;
 
-        root.find('.global-assistant-entry').on('click', function() {
+        applyEntryPosition();
+
+        entry.on('click', function(event) {
+            if (suppressEntryClick) {
+                event.preventDefault();
+                suppressEntryClick = false;
+                return;
+            }
             panel.addClass('show').attr('aria-hidden', 'false');
             $(this).hide();
             input.trigger('focus');
             scrollToBottom();
         });
 
+        entry.on('pointerdown', function(event) {
+            const pointer = event.originalEvent;
+            if (pointer.pointerType === 'mouse' && pointer.button !== 0) return;
+            entryDrag = {
+                pointerId: pointer.pointerId,
+                startX: pointer.clientX,
+                startY: pointer.clientY,
+                positionX: entryPosition.x,
+                positionY: entryPosition.y,
+                moved: false,
+            };
+            suppressEntryClick = false;
+            this.setPointerCapture(pointer.pointerId);
+            entry.addClass('dragging');
+            event.preventDefault();
+        });
+
+        entry.on('pointermove', function(event) {
+            const pointer = event.originalEvent;
+            if (!entryDrag || pointer.pointerId !== entryDrag.pointerId) return;
+            const offsetX = entryDrag.startX - pointer.clientX;
+            const offsetY = entryDrag.startY - pointer.clientY;
+            if (Math.abs(offsetX) > 3 || Math.abs(offsetY) > 3) entryDrag.moved = true;
+            entryPosition = clampEntryPosition({
+                x: entryDrag.positionX + offsetX,
+                y: entryDrag.positionY + offsetY,
+            });
+            applyEntryPosition();
+            event.preventDefault();
+        });
+
+        entry.on('pointerup pointercancel', function(event) {
+            const pointer = event.originalEvent;
+            if (!entryDrag || pointer.pointerId !== entryDrag.pointerId) return;
+            suppressEntryClick = event.type === 'pointerup' && entryDrag.moved;
+            if (this.hasPointerCapture(pointer.pointerId)) this.releasePointerCapture(pointer.pointerId);
+            entryDrag = null;
+            entry.removeClass('dragging');
+            saveEntryPosition();
+        });
+
+        $(window).on('resize.globalAssistant', function() {
+            entryPosition = clampEntryPosition(entryPosition);
+            applyEntryPosition();
+            saveEntryPosition();
+        });
+
         root.find('.global-assistant-close').on('click', function() {
             panel.removeClass('show').attr('aria-hidden', 'true');
             root.find('.global-assistant-entry').show();
@@ -221,5 +280,37 @@
             const last = history.slice().reverse().find(item => item.role === 'assistant' && item.data);
             return last && last.data.conversationId ? last.data.conversationId : '';
         }
+
+        function loadEntryPosition() {
+            try {
+                const value = JSON.parse(localStorage.getItem(entryPositionKey) || '{}');
+                return clampEntryPosition({ x: Number(value.x) || 0, y: Number(value.y) || 0 });
+            } catch (err) {
+                return { x: 0, y: 0 };
+            }
+        }
+
+        function saveEntryPosition() {
+            try {
+                localStorage.setItem(entryPositionKey, JSON.stringify(entryPosition));
+            } catch (err) {
+                // 浏览器禁用本地存储时仅在当前页面保留按钮位置。
+            }
+        }
+
+        function applyEntryPosition() {
+            entry.css('transform', `translate(${-entryPosition.x}px, ${-entryPosition.y}px)`);
+        }
+
+        function clampEntryPosition(position) {
+            const panelWidth = parseFloat(panel.css('width')) || 420;
+            const panelHeight = Math.min(650, Math.max(0, window.innerHeight - 48));
+            const maxX = Math.max(0, panelWidth - entry.outerWidth());
+            const maxY = Math.max(0, panelHeight - entry.outerHeight());
+            return {
+                x: Math.min(maxX, Math.max(0, Number(position.x) || 0)),
+                y: Math.min(maxY, Math.max(0, Number(position.y) || 0)),
+            };
+        }
     });
 })();