Kaynağa Gözat

修改SmartDialog弹窗组件添加双击功能;删除重复的旧组件;

画安 4 gün önce
ebeveyn
işleme
59192aaa6e

+ 0 - 207
src/components/DropdownSelect.vue

@@ -1,207 +0,0 @@
-<template>
-  <div class="custom-dropdown" ref="dropdown">
-    <div 
-      class="dropdown-trigger" 
-      :class="{ 'is-open': isOpen }" 
-      @click="toggleDropdown"
-    >
-      <span class="trigger-text">{{ currentLabel }}</span>
-      <i class="arrow-icon"></i>
-    </div>
-
-    <transition name="fade">
-      <div class="dropdown-menu" v-show="isOpen">
-        <div class="menu-arrow"></div>
-        
-        <div class="menu-list">
-          <div 
-            class="menu-item" 
-            v-for="item in options" 
-            :key="item.value"
-            :class="{ 'is-active': value === item.value }"
-            @click="selectOption(item)"
-          >
-            {{ item.label }}
-          </div>
-        </div>
-      </div>
-    </transition>
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'DropdownSelect',
-  // 支持 v-model
-  model: {
-    prop: 'value',
-    event: 'change'
-  },
-  props: {
-    // 当前选中的值
-    value: {
-      type: [String, Number],
-      default: ''
-    },
-    // 选项数据,例如 [{ label: '50', value: 50 }, ...]
-    options: {
-      type: Array,
-      default: () => []
-    },
-    // 如果没有匹配项时的默认提示文本
-    placeholder: {
-      type: String,
-      default: '请选择'
-    }
-  },
-  data() {
-    return {
-      isOpen: false
-    };
-  },
-  computed: {
-    // 根据当前 value 查找对应的 label 显示在按钮上
-    currentLabel() {
-      const selected = this.options.find(opt => opt.value === this.value);
-      return selected ? selected.label : this.placeholder;
-    }
-  },
-  mounted() {
-    // 监听全局点击事件,用于点击外部关闭下拉框
-    document.addEventListener('click', this.handleClickOutside);
-  },
-  beforeDestroy() {
-    // 组件销毁时移除监听,防止内存泄漏
-    document.removeEventListener('click', this.handleClickOutside);
-  },
-  methods: {
-    toggleDropdown() {
-      this.isOpen = !this.isOpen;
-    },
-    selectOption(item) {
-      if (this.value !== item.value) {
-        this.$emit('change', item.value); // 触发 v-model 更新
-        this.$emit('select', item);       // 额外提供一个 select 事件供外部使用
-      }
-      this.isOpen = false;
-    },
-    handleClickOutside(event) {
-      // 如果点击的区域不在当前组件内部,则关闭下拉框
-      if (this.$refs.dropdown && !this.$refs.dropdown.contains(event.target)) {
-        this.isOpen = false;
-      }
-    }
-  }
-};
-</script>
-
-<style scoped>
-/* 最外层容器 */
-.custom-dropdown {
-  position: relative;
-  display: inline-block;
-  user-select: none;
-}
-
-/* --- 触发器按钮 (深色边框风格) --- */
-.dropdown-trigger {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 4px 12px;
-  min-width: 80px;
-  background-color: transparent;
-  /* 还原图片中带点透明度的蓝灰色边框 */
-  border: 1px solid rgba(100, 130, 190, 0.6); 
-  color: #ffffff;
-  font-size: 14px;
-  cursor: pointer;
-  transition: all 0.2s;
-}
-
-.dropdown-trigger:hover,
-.dropdown-trigger.is-open {
-  border-color: rgba(140, 180, 255, 0.9);
-}
-
-.trigger-text {
-  margin-right: 8px;
-}
-
-/* 纯 CSS 绘制的下拉箭头 */
-.arrow-icon {
-  width: 0;
-  height: 0;
-  border-left: 4px solid transparent;
-  border-right: 4px solid transparent;
-  border-top: 5px solid #ffffff;
-  transition: transform 0.3s ease;
-}
-
-/* 展开时箭头反转向上 */
-.dropdown-trigger.is-open .arrow-icon {
-  transform: rotate(180deg);
-}
-
-/* --- 下拉菜单容器 (白色气泡风格) --- */
-.dropdown-menu {
-  position: absolute;
-  /* 位于触发器正下方并居中对齐 */
-  top: calc(100% + 10px);
-  left: 50%;
-  transform: translateX(-50%);
-  min-width: 80px;
-  background-color: #ffffff;
-  border-radius: 6px;
-  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
-  /* 确保菜单层级在最上面 */
-  z-index: 1000; 
-}
-
-/* 顶部的小三角指示器 */
-.menu-arrow {
-  position: absolute;
-  top: -5px; /* 向上偏移形成凸起 */
-  left: 50%;
-  transform: translateX(-50%);
-  width: 0;
-  height: 0;
-  border-left: 6px solid transparent;
-  border-right: 6px solid transparent;
-  border-bottom: 6px solid #ffffff;
-}
-
-.menu-list {
-  padding: 8px 0;
-}
-
-/* 内部选项 */
-.menu-item {
-  padding: 6px 16px;
-  font-size: 14px;
-  color: #333333;
-  text-align: center;
-  cursor: pointer;
-  transition: background-color 0.2s, color 0.2s;
-}
-
-.menu-item:hover {
-  background-color: #f0f5ff;
-  color: #4da8ff;
-}
-
-/* 选中状态 */
-.menu-item.is-active {
-  color: #4da8ff;
-  font-weight: bold;
-}
-
-/* Vue 的过渡动画效果 */
-.fade-enter-active, .fade-leave-active {
-  transition: opacity 0.2s, transform 0.2s;
-}
-.fade-enter, .fade-leave-to {
-  opacity: 0;
-  transform: translate(-50%, -5px);
-}
-</style>

+ 0 - 357
src/components/SmartDialog.vue

@@ -1,357 +0,0 @@
-<template>
-  <div
-    v-show="visible"
-    class="smart-dialog"
-    :style="dialogStyle"
-    @mousedown="bringToFront"
-  >
-    <div 
-      class="dialog-header" 
-      :class="{ 'is-draggable': draggable }"
-      @mousedown="startDrag"
-    >
-      <div class="title-content">
-        <slot name="header">
-          <span class="title">{{ title }}</span>
-        </slot>
-      </div>
-      <span class="close-btn" @click.stop="close">✕</span>
-    </div>
-
-    <div class="dialog-divider"></div>
-
-    <div class="dialog-body" :class="{ 'no-padding': noPadding }">
-      <slot></slot>
-    </div>
-
-    <div 
-      v-if="resizable" 
-      class="resize-handle" 
-      @mousedown.prevent="startResize"
-    ></div>
-  </div>
-</template>
-
-<script>
-// 全局 z-index 管理,保证多开时点击的窗口总在最上层
-let globalZIndex = 2000;
-
-export default {
-  name: 'SmartDialog',
-  props: {
-    // 基础控制
-    id: { type: [String, Number], required: true },
-    visible: { type: Boolean, default: false },
-    title: { type: String, default: '提示' },
-    
-    // 定位控制
-    center: { type: Boolean, default: true }, // true: 屏幕居中; false: 跟随 position
-    position: { type: Object, default: () => ({ x: 0, y: 0 }) }, 
-    
-    // 功能开关
-    draggable: { type: Boolean, default: true }, // 是否允许拖拽
-    resizable: { type: Boolean, default: true }, // 是否允许拉伸大小
-    noPadding: { type: Boolean, default: false }, // 内容区无内边距
-
-    // 尺寸配置(支持百分比字符串如 '78%' 或数字像素值如 350)
-    defaultWidth: { type: [Number, String], default: 350 },
-    defaultHeight: { type: [Number, String], default: 250 },
-    minWidth: { type: Number, default: 200 },
-    minHeight: { type: Number, default: 150 }
-  },
-  data() {
-    return {
-      x: 0,
-      y: 0,
-      w: this._parseSize(this.defaultWidth, window.innerWidth),
-      h: this._parseSize(this.defaultHeight, window.innerHeight),
-      zIndex: globalZIndex,
-      isDragging: false,
-      isResizing: false,
-      dragOffset: { x: 0, y: 0 },
-      resizeStart: { x: 0, y: 0, w: 0, h: 0 }
-    };
-  },
-  computed: {
-    dialogStyle() {
-      return {
-        left: `${this.x}px`,
-        top: `${this.y}px`,
-        width: `${this.w}px`,
-        height: `${this.h}px`,
-        zIndex: this.zIndex
-      };
-    }
-  },
-  mounted() {
-    window.addEventListener('resize', this._onWindowResize);
-    if (this.visible) {
-      this.bringToFront();
-      this.calculatePosition();
-    }
-  },
-  watch: {
-    visible(newVal) {
-      if (newVal) {
-        this.bringToFront();
-        this.calculatePosition();
-      }
-    },
-    position: {
-      deep: true,
-      handler() {
-        if (this.visible && !this.center) {
-          this.calculatePosition();
-        }
-      }
-    }
-  },
-  methods: {
-    _parseSize(value, base) {
-      if (typeof value === 'string' && value.endsWith('%')) {
-        return Math.round((parseFloat(value) / 100) * base);
-      }
-      return Number(value);
-    },
-    _onWindowResize() {
-      // 只对百分比尺寸的弹窗重新计算
-      const isPercentW = typeof this.defaultWidth === 'string' && this.defaultWidth.endsWith('%');
-      const isPercentH = typeof this.defaultHeight === 'string' && this.defaultHeight.endsWith('%');
-      if (isPercentW) this.w = this._parseSize(this.defaultWidth, window.innerWidth);
-      if (isPercentH) this.h = this._parseSize(this.defaultHeight, window.innerHeight);
-      if ((isPercentW || isPercentH) && this.visible) {
-        this.calculatePosition();
-      }
-    },
-    close() {
-      this.$emit('update:visible', false);
-      this.$emit('close');
-    },
-    bringToFront() {
-      globalZIndex++;
-      this.zIndex = globalZIndex;
-    },
-    // 【核心修改】带有智能偏移(级联叠加)的位置计算
-    calculatePosition() {
-      this.$nextTick(() => {
-        const winWidth = window.innerWidth;
-        const winHeight = window.innerHeight;
-        
-        // 1. 先计算出理论上的“理想位置”
-        let targetX = 0;
-        let targetY = 0;
-
-        if (this.center) {
-          // 居中模式的理想位置
-          targetX = Math.max(0, (winWidth - this.w) / 2);
-          targetY = Math.max(0, (winHeight - this.h) / 2);
-        } else {
-          // 跟随鼠标模式的理想位置
-          targetX = this.position.x || 0;
-          targetY = this.position.y || 0;
-        }
-
-        // ==========================================
-        // 2. 【新增逻辑】碰撞检测与智能偏移 (级联效果)
-        // ==========================================
-        const offsetStep = 20; // 每次偏移 20 像素
-        let collision = true;
-        let attempts = 0; // 防止极端情况下的死循环
-
-        // 获取页面上当前所有的弹窗 DOM 元素
-        const existingDialogs = document.querySelectorAll('.smart-dialog');
-
-        // 只要发现有重叠,就一直偏移,最多尝试 15 次
-        while (collision && attempts < 15) {
-          collision = false;
-          
-          for (let i = 0; i < existingDialogs.length; i++) {
-            const el = existingDialogs[i];
-            
-            // 跳过自己,跳过隐藏的弹窗
-            if (el === this.$el || el.style.display === 'none') continue; 
-
-            const rect = el.getBoundingClientRect();
-            
-            // 坐标重合检测 (加入 2px 的容差,防止浮点数计算误差)
-            const isSameX = Math.abs(rect.left - targetX) < 2;
-            const isSameY = Math.abs(rect.top - targetY) < 2;
-
-            if (isSameX && isSameY) {
-              collision = true;
-              break; // 发现重叠,跳出 for 循环,准备进行偏移
-            }
-          }
-
-          // 如果发生重叠,向右下方偏移
-          if (collision) {
-            targetX += offsetStep;
-            targetY += offsetStep;
-            attempts++;
-          }
-        }
-        // ==========================================
-
-        // 3. 最终的边界安全检测 (防止偏移后右侧或底部超出屏幕)
-        if (targetX + this.w > winWidth) targetX = winWidth - this.w - 10;
-        if (targetY + this.h > winHeight) targetY = winHeight - this.h - 10;
-
-        // 4. 赋值生效
-        this.x = Math.max(0, targetX);
-        this.y = Math.max(0, targetY);
-      });
-    },
-
-    // --- 拖拽逻辑 ---
-    startDrag(e) {
-      // 检查开关,如果不允许拖拽或是点击了关闭按钮,则直接返回
-      if (!this.draggable || e.target.classList.contains('close-btn')) return;
-      
-      this.isDragging = true;
-      this.dragOffset.x = e.clientX - this.x;
-      this.dragOffset.y = e.clientY - this.y;
-      
-      document.addEventListener('mousemove', this.onDrag);
-      document.addEventListener('mouseup', this.stopDrag);
-    },
-    onDrag(e) {
-      if (!this.isDragging) return;
-      // 附加:简单的边界限制,防止头部被拖出可视区导致无法再拖回来
-      let newX = e.clientX - this.dragOffset.x;
-      let newY = e.clientY - this.dragOffset.y;
-      
-      this.x = Math.max(0, Math.min(newX, window.innerWidth - this.w));
-      this.y = Math.max(0, Math.min(newY, window.innerHeight - 40)); // 40是头部高度
-    },
-    stopDrag() {
-      this.isDragging = false;
-      document.removeEventListener('mousemove', this.onDrag);
-      document.removeEventListener('mouseup', this.stopDrag);
-    },
-
-    // --- 拉伸逻辑 ---
-    startResize(e) {
-      if (!this.resizable) return;
-      
-      this.isResizing = true;
-      this.resizeStart = { x: e.clientX, y: e.clientY, w: this.w, h: this.h };
-      
-      document.addEventListener('mousemove', this.onResize);
-      document.addEventListener('mouseup', this.stopResize);
-    },
-    onResize(e) {
-      if (!this.isResizing) return;
-      const deltaX = e.clientX - this.resizeStart.x;
-      const deltaY = e.clientY - this.resizeStart.y;
-      
-      this.w = Math.max(this.minWidth, this.resizeStart.w + deltaX);
-      this.h = Math.max(this.minHeight, this.resizeStart.h + deltaY);
-    },
-    stopResize() {
-      this.isResizing = false;
-      document.removeEventListener('mousemove', this.onResize);
-      document.removeEventListener('mouseup', this.stopResize);
-    }
-  },
-  beforeDestroy() {
-    window.removeEventListener('resize', this._onWindowResize);
-    document.removeEventListener('mousemove', this.onDrag);
-    document.removeEventListener('mouseup', this.stopDrag);
-    document.removeEventListener('mousemove', this.onResize);
-    document.removeEventListener('mouseup', this.stopResize);
-  }
-};
-</script>
-
-<style scoped>
-/* 1. 弹窗主容器:毛玻璃与发光背景 */
-.smart-dialog {
-  position: fixed;
-  /* 使用半透明深蓝色,配合一点点径向渐变模拟左侧高光 */
-  background: radial-gradient(circle at 20% 0%, rgba(40, 120, 200, 0.4) 0%, rgba(20, 60, 130, 0.8) 70%);
-  border: 1px solid rgba(255, 255, 255, 0.15); /* 极细的半透明白边 */
-  border-radius: 12px; /* 更大的圆角,贴合图片 */
-  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4); /* 更柔和深邃的阴影 */
-  backdrop-filter: blur(12px); /* 核心:毛玻璃模糊效果 */
-  -webkit-backdrop-filter: blur(12px); /* Safari 兼容 */
-  
-  display: flex;
-  flex-direction: column;
-  overflow: hidden;
-  user-select: none;
-}
-
-/* 2. 头部区域:去除背景,调整内边距 */
-.dialog-header {
-  height: auto;
-  background: transparent; /* 去除原本的渐变色,融入整体背景 */
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  padding: 16px 20px 12px 20px; /* 上右下左留白 */
-}
-
-.dialog-header.is-draggable {
-  cursor: move;
-}
-
-/* 标题容器:自适应填充 */
-.title-content {
-  flex: 1;
-  min-width: 0;
-}
-
-/* 标题样式:纯白、加粗 */
-.title {
-  color: #ffffff;
-  font-size: 16px;
-  font-weight: 600;
-  letter-spacing: 1px;
-}
-
-/* 关闭按钮:干净细致的 X */
-.close-btn {
-  cursor: pointer;
-  color: #ffffff;
-  font-size: 18px;
-  line-height: 1;
-  font-weight: 300;
-  opacity: 0.8;
-  transition: all 0.2s;
-}
-.close-btn:hover {
-  opacity: 1;
-  transform: scale(1.1);
-}
-
-/* 3. 【核心】独立的分割线 */
-.dialog-divider {
-  height: 1px;
-  background-color: rgba(255, 255, 255, 0.3); /* 半透明白线 */
-  margin: 0 20px; /* 左右留白,不撑满屏幕 */
-}
-
-/* 4. 内容区与拉伸把手 */
-.dialog-body {
-  flex: 1;
-  min-height: 0;
-  padding: 20px;
-  overflow: hidden;
-}
-.dialog-body.no-padding {
-  padding: 0;
-  color: #e2e8f0;
-  overflow: hidden;
-  cursor: default;
-}
-
-.resize-handle {
-  position: absolute;
-  right: 0;
-  bottom: 0;
-  width: 16px;
-  height: 16px;
-  cursor: se-resize;
-  background: linear-gradient(135deg, transparent 50%, rgba(255,255,255,0.2) 50%);
-}
-</style>

+ 21 - 8
src/components/ui/SmartDialog.vue

@@ -1,5 +1,5 @@
 <template>
-  <div v-show="visible" class="smart-dialog" :style="dialogStyle" @mousedown="bringToFront">
+  <div v-show="visible" class="smart-dialog" :style="dialogStyle" @mousedown="bringToFront" @dblclick="handleDoubleClick">
     <div class="dialog-header" :class="{ 'is-draggable': draggable }" @mousedown="startDrag" v-if="title">
       <div class="title-content">
         <slot name="header">
@@ -21,12 +21,10 @@
 </template>
 
 <script>
-// 1. 【核心修改】:删除原本引入的 pageScale
-// import { pageScale } from '@/utils/rem.js';
 
 let globalZIndex = 2000;
 
-// 2. 【核心修改】:在这里定义你的大屏设计稿基准宽度 (通常是 1920)
+// 在这里定义你的大屏设计稿基准宽度 (通常是 1920)
 const DESIGN_WIDTH = 1920; 
 
 export default {
@@ -47,7 +45,8 @@ export default {
     defaultWidth: { type: [Number, String], default: 350 },
     defaultHeight: { type: [Number, String], default: 250 },
     minWidth: { type: Number, default: 200 },
-    minHeight: { type: Number, default: 150 }
+    minHeight: { type: Number, default: 150 },
+    enableDblclickExpand: { type: Boolean, default: false }, 
   },
   data() {
     return {
@@ -70,7 +69,8 @@ export default {
         top: `${this.y}px`,
         width: `${this.w}px`,
         height: `${this.h}px`,
-        zIndex: this.zIndex
+        zIndex: this.zIndex,
+        cursor: this.enableDblclickExpand ? 'pointer' : 'default'
       };
     }
   },
@@ -104,7 +104,20 @@ export default {
     }
   },
   methods: {
-    // 3. 【新增方法】:弹窗自己实时计算当前屏幕相当于设计稿的缩放比例
+    // 处理整个弹窗的双击事件
+    handleDoubleClick(e) {
+      // 1. 如果没开启这个功能,直接无视
+      if (!this.enableDblclickExpand) return;
+      
+      // 2. 防误触:如果双击的是关闭按钮或者拉伸把手,不要触发
+      if (e.target.classList.contains('close-btn') || e.target.classList.contains('resize-handle')) {
+        return; 
+      }
+
+      // 3. 告诉外层父组件:“我被双击了,请处理下一步”
+      this.$emit('expand', this.id); 
+    },
+    // 弹窗自己实时计算当前屏幕相当于设计稿的缩放比例
     getRealScale() {
       return window.innerWidth / DESIGN_WIDTH;
     },
@@ -113,7 +126,7 @@ export default {
       if (typeof value === 'string' && value.endsWith('%')) {
         return Math.round((parseFloat(value) / 100) * base);
       }
-      // 4. 【核心修改】:使用内部实时计算的 scale,绝不出错
+      // 使用内部实时计算的 scale,绝不出错
       const scale = this.getRealScale();
       return Number(value) * scale;
     },

+ 9 - 2
src/views/StatusMonitoring.vue

@@ -50,8 +50,9 @@
         <template #dialogs>
             <SmartDialog v-for="dialog in activeDialogs" :key="dialog.id" :id="dialog.id" :visible.sync="dialog.visible"
                 :title="dialog.title" :defaultWidth="dialog.width || 400" :defaultHeight="dialog.height || 300"
-                :center="dialog.center !== false" :position="dialog.position" :showClose="dialog.showClose"
-                @close="handleDialogClose(dialog.id)">
+                :center="dialog.center !== false" :position="dialog.position" :showClose="dialog.showClose" :enableDblclickExpand="dialog.enableDblclickExpand"
+
+                @close="handleDialogClose(dialog.id)" @expand="handleDoubleClickExpend(dialog.id)">
 
                 <component :is="dialog.componentName" v-bind="dialog.data"></component>
             </SmartDialog>
@@ -237,6 +238,10 @@ export default {
             this.testOpenSecurityRoute2();
             this.testOpenTrafficTimeSpace();
         },
+        // 处理弹窗双击事件
+        handleDoubleClickExpend(id) {
+            console.log('处理弹窗双击事件', id);
+        },
         openDialog(config) {
             // 1. 防止重复打开同一个弹窗 (根据 id 判断)
             const existingDialog = this.activeDialogs.find(d => d.id === config.id);
@@ -257,6 +262,7 @@ export default {
                 height: config.height || 300,      // 自定义高度
                 center: config.center !== false,   // 是否居中显示
                 position: config.position || null, // 自定义坐标 {x, y}
+                enableDblclickExpand: config.enableDblclickExpand !== false, // 是否启用双击
                 showClose: config.showClose !== false, // 是否显示关闭按钮
                 data: config.data || {}            // 传给内部组件的业务数据
             });
@@ -320,6 +326,7 @@ export default {
                 width: 300,
                 height: 200,
                 center: false,
+                enableDblclickExpand: true,
                 position: { x: 1100, y: 200 },
                 data: {
                     mapData: {},