Summernote模态对话框定制:ModalUI组件扩展方法

Summernote模态对话框定制:ModalUI组件扩展方法

【免费下载链接】summernote Super simple WYSIWYG editor 【免费下载链接】summernote 项目地址: https://gitcode.com/gh_mirrors/su/summernote

1. 模态对话框架构解析

Summernote编辑器的模态对话框(Modal Dialog)系统基于ModalUI组件构建,该组件位于src/styles/lite/js/ModalUI.js路径下。作为轻量化编辑器的核心交互模块,它负责管理对话框的显示/隐藏状态、键盘事件监听和背景遮罩层控制。

1.1 核心类结构

class ModalUI {
  constructor($node /*, options */) {
    this.$modal = $node;                  // 对话框DOM元素
    this.$backdrop = $('<div class="note-modal-backdrop"></div>'); // 背景遮罩
  }

  show() { /* 显示对话框逻辑 */ }
  hide() { /* 隐藏对话框逻辑 */ }
}

1.2 工作流程

mermaid

2. 基础定制方法

2.1 样式覆盖技术

通过自定义CSS可以修改对话框的视觉表现,无需修改核心代码。Summernote的模态框使用note-modal前缀的CSS类名,主要可定制元素包括:

/* 自定义对话框尺寸 */
.note-modal.open {
  width: 80%;            /* 默认宽度60% */
  max-width: 1000px;     /* 最大宽度限制 */
  margin-left: -40%;     /* 居中修正(宽度一半) */
}

/* 自定义背景遮罩 */
.note-modal-backdrop {
  background-color: rgba(0, 0, 0, 0.7); /* 默认0.5透明度 */
  backdrop-filter: blur(5px);           /* 添加毛玻璃效果 */
}

/* 自定义标题栏 */
.note-modal .modal-header {
  background-color: #f5f7fa;
  border-bottom: 2px solid #e1e4e8;
  padding: 12px 16px;
}

2.2 事件监听与扩展

ModalUI组件在显示和隐藏时会触发特定事件,可通过这些事件实现扩展功能:

// 监听对话框显示事件
$(document).on('note.modal.show', '.note-modal', function(e) {
  const $modal = $(this);
  
  // 示例:添加自定义水印
  if ($modal.find('.modal-title').text() === '图片上传') {
    $modal.find('.modal-body').append(
      '<div style="position:absolute; bottom:10px; right:10px; color:#ccc;">' +
      '企业版功能:支持批量上传' +
      '</div>'
    );
  }
});

// 监听对话框隐藏事件
$(document).on('note.modal.hide', '.note-modal', function(e) {
  // 示例:清理临时数据
  $(this).find('input[type="file"]').val('');
});

3. 高级功能扩展

3.1 组件继承扩展

通过ES6类继承可以安全地扩展ModalUI功能,避免直接修改源码导致升级困难:

import ModalUI from './src/styles/lite/js/ModalUI.js';

class AdvancedModalUI extends ModalUI {
  constructor($node, options = {}) {
    super($node);
    this.options = {
      draggable: false,       // 是否可拖拽
      resizeable: false,      // 是否可调整大小
      ...options
    };
    
    // 初始化高级功能
    if (this.options.draggable) {
      this._initDraggable();
    }
    if (this.options.resizeable) {
      this._initResizeable();
    }
  }

  // 重写show方法添加动画效果
  show() {
    super.show();
    this.$modal.css({
      opacity: 0,
      transform: 'translate(-50%, -30%)'
    }).animate({
      opacity: 1,
      transform: 'translate(-50%, -50%)'
    }, 300);
  }

  // 初始化拖拽功能
  _initDraggable() {
    const $header = this.$modal.find('.modal-header');
    let startX, startY, initialLeft, initialTop;
    
    $header.css('cursor', 'move').on('mousedown', (e) => {
      e.preventDefault();
      startX = e.clientX;
      startY = e.clientY;
      initialLeft = parseInt(this.$modal.css('left'));
      initialTop = parseInt(this.$modal.css('top'));
      
      $(document).on('mousemove.drag', (e) => {
        const dx = e.clientX - startX;
        const dy = e.clientY - startY;
        this.$modal.css({
          left: initialLeft + dx,
          top: initialTop + dy
        });
      }).on('mouseup.drag', () => {
        $(document).off('mousemove.drag mouseup.drag');
      });
    });
  }

  // 初始化调整大小功能
  _initResizeable() {
    const resizer = $('<div class="modal-resizer"></div>').appendTo(this.$modal);
    resizer.css({
      position: 'absolute',
      bottom: '5px',
      right: '5px',
      width: '15px',
      height: '15px',
      cursor: 'se-resize',
      backgroundColor: '#337ab7'
    });
    
    let startX, startY, initialWidth, initialHeight;
    
    resizer.on('mousedown', (e) => {
      e.preventDefault();
      startX = e.clientX;
      startY = e.clientY;
      initialWidth = this.$modal.width();
      initialHeight = this.$modal.height();
      
      $(document).on('mousemove.resize', (e) => {
        const dx = e.clientX - startX;
        const dy = e.clientY - startY;
        this.$modal.css({
          width: Math.max(400, initialWidth + dx),  // 最小宽度400px
          height: Math.max(300, initialHeight + dy) // 最小高度300px
        });
      }).on('mouseup.resize', () => {
        $(document).off('mousemove.resize mouseup.resize');
      });
    });
  }
}

// 替换原始ModalUI
window.ModalUI = AdvancedModalUI;

3.2 多标签对话框实现

通过扩展ModalUI可实现支持多标签页的复杂对话框,适用于需要在单个弹窗中展示多个功能面板的场景:

class TabbedModalUI extends ModalUI {
  constructor($node, tabs = []) {
    super($node);
    this.tabs = tabs;         // 标签配置
    this.activeTabId = null;  // 当前激活标签ID
    
    this._renderTabs();       // 渲染标签栏
    this._bindTabEvents();    // 绑定标签切换事件
  }

  // 渲染标签栏
  _renderTabs() {
    if (this.tabs.length <= 1) return; // 单标签无需渲染
    
    const tabBar = $('<ul class="modal-tab-bar"></ul>');
    
    // 创建标签按钮
    this.tabs.forEach(tab => {
      const tabBtn = $(`
        <li class="modal-tab ${tab.active ? 'active' : ''}" data-tab-id="${tab.id}">
          ${tab.icon ? `<i class="${tab.icon}"></i> ` : ''}${tab.title}
        </li>
      `);
      tabBar.append(tabBtn);
      
      // 设置默认激活标签
      if (tab.active) {
        this.activeTabId = tab.id;
      }
    });
    
    // 添加到对话框头部
    this.$modal.find('.modal-header').append(tabBar);
    
    // 创建标签内容容器
    this.$modal.find('.modal-body').wrapInner('<div class="modal-tab-content"></div>');
    this.$tabContent = this.$modal.find('.modal-tab-content');
  }

  // 绑定标签切换事件
  _bindTabEvents() {
    this.$modal.on('click', '.modal-tab', (e) => {
      const tabId = $(e.currentTarget).data('tab-id');
      this.switchTab(tabId);
    });
  }

  // 切换标签页
  switchTab(tabId) {
    if (tabId === this.activeTabId) return;
    
    // 更新标签按钮状态
    this.$modal.find('.modal-tab').removeClass('active');
    this.$modal.find(`.modal-tab[data-tab-id="${tabId}"]`).addClass('active');
    
    // 触发标签切换事件
    this.$modal.trigger('note.modal.tab.switch', {
      prevTabId: this.activeTabId,
      newTabId: tabId
    });
    
    this.activeTabId = tabId;
  }
}

// 使用示例
const imageModal = new TabbedModalUI($('#image-modal'), [
  { id: 'upload', title: '上传图片', icon: 'note-icon-upload', active: true },
  { id: 'url', title: 'URL插入', icon: 'note-icon-link' },
  { id: 'library', title: '图片库', icon: 'note-icon-picture' }
]);

配套CSS样式:

/* 标签栏样式 */
.modal-tab-bar {
  list-style: none;
  padding: 0;
  margin: 0 0 0 15px;
  display: inline-flex;
  border-bottom: none;
}

.modal-tab {
  padding: 8px 15px;
  margin: 0 2px;
  cursor: pointer;
  border-radius: 4px 4px 0 0;
  border: 1px solid transparent;
  border-bottom: none;
}

.modal-tab.active {
  background-color: #fff;
  border-color: #ddd;
  border-top: 2px solid #337ab7;
}

/* 标签内容区域 */
.modal-tab-content > div {
  display: none;
}

.modal-tab-content > div.active {
  display: block;
}

4. 与编辑器集成方法

4.1 自定义对话框注册

扩展后的模态组件需要注册到Summernote编辑器才能正常使用,典型的集成流程如下:

// 定义自定义对话框模块
const CustomImageDialog = (context) => {
  const ui = $.summernote.ui;
  const $editor = context.layoutInfo.editor;
  
  // 创建对话框元素
  const $dialog = ui.dialog({
    title: '高级图片管理',
    body: '<div id="custom-image-dialog-content">加载中...</div>',
    buttons: [
      {
        text: '取消',
        action: function(dialog) {
          dialog.close();
        }
      },
      {
        text: '插入',
        class: 'btn-primary',
        action: function(dialog) {
          // 处理插入逻辑
          const imageUrl = $('#image-url-input').val();
          context.invoke('editor.insertImage', imageUrl);
          dialog.close();
        }
      }
    ]
  });

  // 使用高级模态组件
  const advancedModal = new AdvancedModalUI($dialog.$modal[0], {
    draggable: true,
    resizeable: true
  });

  // 重写对话框显示方法
  const showDialog = () => {
    // 加载对话框内容
    $('#custom-image-dialog-content').load('image-dialog-content.html', () => {
      advancedModal.show();
    });
  };

  // 注册按钮到工具栏
  context.memo('button.customImage', () => {
    return ui.button({
      contents: '<i class="note-icon-picture"></i>',
      tooltip: '高级图片管理',
      click: showDialog
    }).render();
  });

  return {
    showDialog: showDialog
  };
};

// 注册为Summernote模块
$.summernote.addPlugin({
  name: 'customImageDialog',
  modules: {
    customImageDialog: CustomImageDialog
  }
});

4.2 配置参数说明

扩展ModalUI时可通过配置参数控制各种行为,下表列出主要可配置项及默认值:

参数名类型默认值说明
draggableBooleanfalse是否允许拖拽对话框
resizeableBooleanfalse是否允许调整大小
backdropBooleantrue是否显示背景遮罩
keyboardBooleantrue是否支持ESC键关闭
animationBooleanfalse是否启用显示/隐藏动画
widthNumber/String'60%'对话框宽度,支持像素值或百分比
heightNumber/String'auto'对话框高度,支持像素值或'auto'
maxWidthNumber1200最大宽度限制
maxHeightNumber800最大高度限制
onShowFunctionnull对话框显示回调函数
onHideFunctionnull对话框隐藏回调函数

5. 常见问题解决方案

5.1 对话框定位异常

问题描述:自定义对话框在某些屏幕尺寸下出现定位偏移或溢出。

解决方案:通过动态计算定位值确保对话框居中:

// 修复对话框定位
AdvancedModalUI.prototype.fixPosition = function() {
  const $window = $(window);
  const modalWidth = this.$modal.outerWidth();
  const modalHeight = this.$modal.outerHeight();
  const windowWidth = $window.width();
  const windowHeight = $window.height();
  
  // 计算居中位置
  const left = Math.max(0, (windowWidth - modalWidth) / 2);
  const top = Math.max(0, (windowHeight - modalHeight) / 2);
  
  this.$modal.css({ left: `${left}px`, top: `${top}px` });
};

// 监听窗口大小变化自动调整
$(window).on('resize', () => {
  if (advancedModal && advancedModal.$modal.hasClass('open')) {
    advancedModal.fixPosition();
  }
});

5.2 多次点击导致多重背景遮罩

问题根源:ModalUI默认每次调用show()都会创建新的背景遮罩元素。

解决方案:修改构造函数确保背景遮罩单例:

constructor($node, options) {
  super($node, options);
  
  // 单例背景遮罩
  const existingBackdrop = document.querySelector('.note-modal-backdrop');
  if (existingBackdrop) {
    this.$backdrop = $(existingBackdrop);
  } else {
    this.$backdrop = $('<div class="note-modal-backdrop"></div>');
  }
}

5.3 表单元素焦点问题

问题描述:对话框中的表单元素无法获取焦点或键盘事件被拦截。

解决方案:在show()方法中主动设置焦点并优化事件委托:

show() {
  super.show();
  
  // 设置初始焦点
  setTimeout(() => {
    const $firstInput = this.$modal.find('input, textarea, select').first();
    if ($firstInput.length) {
      $firstInput.focus();
    }
  }, 100);
  
  // 优化表单事件委托
  this.$modal.on('focusin', (e) => {
    // 阻止事件冒泡到编辑器,避免编辑器获取焦点
    e.stopPropagation();
  });
}

6. 最佳实践与注意事项

6.1 扩展原则

  1. 最小侵入性:优先使用继承而非修改源码,便于后续升级
  2. 事件驱动:通过事件系统实现组件通信,避免直接方法调用
  3. 样式隔离:自定义样式使用独特类名前缀,避免样式冲突
  4. 兼容性:考虑不同浏览器对CSS特性的支持情况,必要时提供降级方案

6.2 性能优化

  1. 延迟加载:复杂对话框内容采用按需加载,减少初始渲染时间
  2. 事件委托:将事件监听器绑定到对话框根元素,避免大量子元素绑定
  3. DOM缓存:缓存频繁访问的DOM元素引用,减少选择器查询开销
  4. 动画优化:使用CSS transform和opacity属性实现动画,避免重排重绘

6.3 测试策略

mermaid

7. 总结与扩展方向

Summernote的ModalUI组件提供了基础但灵活的对话框功能,通过本文介绍的继承扩展、事件监听和样式定制等方法,可以满足从简单样式调整到复杂多标签对话框的各类需求。

未来扩展方向包括:

  1. 拖拽排序功能:集成SortableJS实现对话框内元素的拖拽排序
  2. 富文本内容支持:在对话框中嵌入迷你Summernote编辑器
  3. 分步向导:基于多标签对话框实现带进度指示的分步操作流程
  4. 响应式适配:针对移动设备优化的自适应对话框布局

通过合理利用ModalUI的扩展能力,可以显著提升Summernote编辑器的交互体验,满足更复杂的业务场景需求。建议在扩展过程中保持组件的独立性和可复用性,遵循Summernote的模块化设计理念。

【免费下载链接】summernote Super simple WYSIWYG editor 【免费下载链接】summernote 项目地址: https://gitcode.com/gh_mirrors/su/summernote

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值