From 5e2a9c48126e4d6a33a080c009cc5795c40afeeb Mon Sep 17 00:00:00 2001 From: sight <26325820+Sight-wcg@users.noreply.github.com> Date: Sat, 21 Jun 2025 22:16:49 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat(tabs):=20=E6=94=AF=E6=8C=81=E6=8B=96?= =?UTF-8?q?=E6=8B=BD=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/tabs/detail/options.md | 14 +++++ src/css/layui.css | 2 + src/modules/tabs.js | 122 +++++++++++++++++++++++++++++++++++- 3 files changed, 135 insertions(+), 3 deletions(-) diff --git a/docs/tabs/detail/options.md b/docs/tabs/detail/options.md index 37b092b4..e7a446dc 100644 --- a/docs/tabs/detail/options.md +++ b/docs/tabs/detail/options.md @@ -99,6 +99,20 @@ `false` + + + +dragSort 2.12+ + + +是否开启标签项拖拽排序功能 + + +boolean + + +`false` + diff --git a/src/css/layui.css b/src/css/layui.css index 14a14745..59d9a4a2 100644 --- a/src/css/layui.css +++ b/src/css/layui.css @@ -1315,6 +1315,8 @@ body .layui-table-tips .layui-layer-content{background: none; padding: 0; box-sh .layui-tabs-scroll .layui-tabs-header:after{display: none; content: none; border: 0;} .layui-tabs-bar .layui-icon{position: absolute; left: 0; top: 0; z-index: 3; width: 40px; height: 100%; line-height: 40px; border: 1px solid #eee; text-align: center; cursor: pointer; box-sizing: border-box; background-color: #fff; box-shadow: 2px 0 5px 0 rgb(0 0 0 / 6%);} .layui-tabs-bar .layui-icon-next{left: auto; right: 0; box-shadow: -2px 0 5px 0 rgb(0 0 0 / 6%);} +.layui-tabs-dragsort-chosen:before {content: ''; height: 100%; width: 2px; background-color: #16baaa; position: absolute; top: 0; left: 0; box-sizing: border-box;} +.layui-tabs-dragsort-chosen *{ pointer-events: none;} .layui-tabs-header li .layui-tabs-close{position: relative; display: inline-block; width: 16px; height: 16px; line-height: 18px; margin-left: 8px; top: 0px; text-align: center; font-size: 12px; color: #959595; border-radius: 50%; font-weight: 700; transition: all .16s; -webkit-transition: all .16s;} .layui-tabs-header li .layui-tabs-close:hover{ background-color: #ff5722; color: #fff;} diff --git a/src/modules/tabs.js b/src/modules/tabs.js index ca188943..b592468e 100644 --- a/src/modules/tabs.js +++ b/src/modules/tabs.js @@ -25,7 +25,8 @@ layui.define('component', function(exports) { CLOSE: 'layui-tabs-close', BODY: 'layui-tabs-body', ITEM: 'layui-tabs-item', - CARD: 'layui-tabs-card' + CARD: 'layui-tabs-card', + DRAG_CHOSEN: 'layui-tabs-dragsort-chosen' }, // 渲染 @@ -151,11 +152,17 @@ layui.define('component', function(exports) { }); inner.onresize = true; } + + // 拖拽事件 + if(options.dragSort){ + that.dragSort(container.header.elem); + } } }); // 内部变量集 var inner = {}; + var rAF = window.requestAnimationFrame || function(fn){return setTimeout(fn, 1000 / 60)}; /** * 扩展组件原型方法 @@ -363,9 +370,10 @@ layui.define('component', function(exports) { * 切换标签 * @param {Object} thisHeaderItem - 当前标签头部项元素 * @param {boolean} [force=false] - 是否强制切换 + * @param {boolean} [disabledScroll=false] - 是否禁用滚动 * @returns */ - Class.prototype.change = function(thisHeaderItem, force) { + Class.prototype.change = function(thisHeaderItem, force, disabledScroll) { if (!thisHeaderItem || !thisHeaderItem[0]) return; var that = this; @@ -418,7 +426,10 @@ layui.define('component', function(exports) { that.findBodyItem(layid || index).addClass(component.CONST.CLASS_SHOW) .siblings().removeClass(component.CONST.CLASS_SHOW); - that.roll('auto', index); + // 标签切换 + if (!disabledScroll) { + that.roll('auto', index); + } // 重新获取标签相关数据 var data = that.data(); @@ -714,6 +725,111 @@ layui.define('component', function(exports) { }; }; + /** + * 拖拽排序 + * @param {jQuery} headerElem - 拖拽的头部容器元素 + */ + Class.prototype.dragSort = function(headerElem) { + var that = this; + var namespace = '.lay-tabs-drag'; + var container = headerElem.parent(); + var scrollable = container.hasClass('layui-tabs-scroll'); + var draggedElem; + var scrollWidth = headerElem.prop('scrollWidth'); + var outerWidth = headerElem.outerWidth(); + var scrollThreshold = 100; + + var isDndElem = function(el) { + return el && el.nodeName.toLowerCase() == 'li' && !!el.getAttribute('lay-id'); + }; + + headerElem.off(namespace); + headerElem.on('mousedown' + namespace, '>li', function() { + if(isDndElem(this)){ + this.draggable = true; + } + }); + headerElem.on('dragstart' + namespace, function(e) { + e.originalEvent.dataTransfer.effectAllowed = 'move'; + draggedElem = $(e.target); + that.change(draggedElem, false, true); + }); + headerElem.on('dragenter' + namespace, function(e) { + if(isDndElem(e.target)) { + $(e.target).addClass(component.CONST.DRAG_CHOSEN); + } + }); + headerElem.on("dragover" + namespace, function (e) { + e.preventDefault(); + e.originalEvent.dataTransfer.dropEffect = "move"; + + // 拖拽边缘滚动 + if (scrollable) { + var tabLeft = headerElem.data("left") || 0; + var containerRect = container[0].getBoundingClientRect(); + var step = 200; + var rAFStep = 5; + var isScrollLeft = e.clientX - containerRect.left < scrollThreshold; + var isScrollRight = containerRect.right - e.clientX < scrollThreshold; + + if (!isScrollLeft && !isScrollRight) { + return; + } + + var cb = function () { + if (step > 0) { + step -= rAFStep; + if (isScrollLeft) { + tabLeft += rAFStep; + } else if (isScrollRight) { + tabLeft += -rAFStep; + } + if (tabLeft > 0) { + tabLeft = 0; + } else if (tabLeft < outerWidth - scrollWidth) { + tabLeft = outerWidth - scrollWidth; + } + headerElem.css("left", tabLeft).data("left", tabLeft); + rAF(cb); + } + }; + rAF(cb); + } + }); + headerElem.on('dragleave' + namespace, function(e) { + if(isDndElem(e.target)) { + $(e.target).removeClass(component.CONST.DRAG_CHOSEN); + } + }); + headerElem.on('drop' + namespace, function(e) { + e.preventDefault(); + if (isDndElem(e.target)) { + var dropTargetElem = $(e.target); + dropTargetElem.removeClass(component.CONST.DRAG_CHOSEN); + var dragIndex = that.data().thisHeaderItem.index(); + var dropIndex = dropTargetElem.index(); + + if(dragIndex > dropIndex) { + dropTargetElem.before(draggedElem); + } else { + dropTargetElem.after(draggedElem); + } + } + }); + headerElem.on('dragend' + namespace, function(e) { + e.preventDefault(); + draggedElem = null; + + var data = that.data(); + layui.event.call( + data.thisHeaderItem[0], + component.CONST.MOD_NAME, + 'dragend(' + that.config.id +')', + that.data() + ); + }); + }; + // 扩展组件接口 $.extend(component, { /** From b6310b8a8311dcc23a61d6d651eeafd5458504b8 Mon Sep 17 00:00:00 2001 From: sight <26325820+Sight-wcg@users.noreply.github.com> Date: Sun, 22 Jun 2025 01:15:16 +0800 Subject: [PATCH 2/5] update --- docs/tabs/index.md | 13 +++++++++++++ src/modules/tabs.js | 35 +++++++++++++++++++++-------------- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/docs/tabs/index.md b/docs/tabs/index.md index de26219a..4c2aabb6 100644 --- a/docs/tabs/index.md +++ b/docs/tabs/index.md @@ -357,6 +357,19 @@ tabs.on('afterClose(testID)', function(data) { }); ``` +

标签拖拽结束后的事件 2.12+

+ +`tabs.on('dragend(id)', callback)` + +标签拖拽结束后触发。 + +```js +// tabs 拖拽结束后的事件 +tabs.on('dragend(testID)', function(data) { + console.log(data); +}); +``` + ## 💖 心语 tabs 是通过 component 重构的首个组件,它来自于最早试图发布的 Layui 3.0(后因为 3.0 技术路线的变化,而整理放至 2.10+ 版本中),目的是将 element 模块中的 tab 组件进行解耦,增强其可扩展性。为了给开发者必要的时间缓冲,我们会将旧 tab 组件仍然保留在后续的若干版本中,但会在合适的时机对旧 tab 组件进行剔除,建议开发者尽量提前过渡到当前新的 tabs 组件。 diff --git a/src/modules/tabs.js b/src/modules/tabs.js index b592468e..62a24965 100644 --- a/src/modules/tabs.js +++ b/src/modules/tabs.js @@ -733,11 +733,13 @@ layui.define('component', function(exports) { var that = this; var namespace = '.lay-tabs-drag'; var container = headerElem.parent(); - var scrollable = container.hasClass('layui-tabs-scroll'); var draggedElem; - var scrollWidth = headerElem.prop('scrollWidth'); - var outerWidth = headerElem.outerWidth(); + var scrollable; + var tabListSize; + var containerSize; var scrollThreshold = 100; + var maxOffset = 0; + var minOffset; var isDndElem = function(el) { return el && el.nodeName.toLowerCase() == 'li' && !!el.getAttribute('lay-id'); @@ -747,6 +749,10 @@ layui.define('component', function(exports) { headerElem.on('mousedown' + namespace, '>li', function() { if(isDndElem(this)){ this.draggable = true; + scrollable = container.hasClass('layui-tabs-scroll'); + tabListSize = headerElem.prop('scrollWidth'); + containerSize = headerElem.outerWidth(); + minOffset = containerSize - tabListSize; } }); headerElem.on('dragstart' + namespace, function(e) { @@ -761,14 +767,11 @@ layui.define('component', function(exports) { }); headerElem.on("dragover" + namespace, function (e) { e.preventDefault(); - e.originalEvent.dataTransfer.dropEffect = "move"; + e.originalEvent.dataTransfer.dropEffect = 'move'; // 拖拽边缘滚动 if (scrollable) { - var tabLeft = headerElem.data("left") || 0; var containerRect = container[0].getBoundingClientRect(); - var step = 200; - var rAFStep = 5; var isScrollLeft = e.clientX - containerRect.left < scrollThreshold; var isScrollRight = containerRect.right - e.clientX < scrollThreshold; @@ -776,20 +779,24 @@ layui.define('component', function(exports) { return; } + var step = 35; + var rAFStep = 5; + var newOffset = headerElem.data('left') || 0; + var cb = function () { if (step > 0) { step -= rAFStep; if (isScrollLeft) { - tabLeft += rAFStep; + newOffset = newOffset + rAFStep; } else if (isScrollRight) { - tabLeft += -rAFStep; + newOffset = newOffset - rAFStep; } - if (tabLeft > 0) { - tabLeft = 0; - } else if (tabLeft < outerWidth - scrollWidth) { - tabLeft = outerWidth - scrollWidth; + if (newOffset > maxOffset) { + newOffset = maxOffset; + } else if (newOffset < minOffset) { + newOffset = minOffset; } - headerElem.css("left", tabLeft).data("left", tabLeft); + headerElem.css('left', newOffset).data('left', newOffset); rAF(cb); } }; From 510ce309160f826fecde2798ba6b37851ac3036c Mon Sep 17 00:00:00 2001 From: sight <26325820+Sight-wcg@users.noreply.github.com> Date: Sun, 22 Jun 2025 13:51:11 +0800 Subject: [PATCH 3/5] update --- examples/tabs.html | 26 +++++++++++++------------- src/modules/tabs.js | 24 +++++++++++++----------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/examples/tabs.html b/examples/tabs.html index efc73c72..31d065a2 100644 --- a/examples/tabs.html +++ b/examples/tabs.html @@ -9,22 +9,22 @@

动态操作

-
+
    -
  • Tab1
  • -
  • Tab2
  • -
  • Tab3
  • -
  • Tab4
  • -
  • Tab5
  • -
  • Tab6
  • +
  • Tab1
  • +
  • Tab2
  • +
  • Tab3
  • +
  • Tab4
  • +
  • Tab5
  • +
  • Tab6
-
Tab Content-1
-
Tab Content-2
-
Tab Content-3
-
Tab Content-4
-
Tab Content-5
-
Tab Content-6
+
Tab Content-1
+
Tab Content-2
+
Tab Content-3
+
Tab Content-4
+
Tab Content-5
+
Tab Content-6
diff --git a/src/modules/tabs.js b/src/modules/tabs.js index 62a24965..2c4c9db8 100644 --- a/src/modules/tabs.js +++ b/src/modules/tabs.js @@ -735,11 +735,13 @@ layui.define('component', function(exports) { var container = headerElem.parent(); var draggedElem; var scrollable; - var tabListSize; - var containerSize; - var scrollThreshold = 100; + var tabListScrollSize; + var tabListOuterSize; var maxOffset = 0; var minOffset; + var dragScrollLength = 35; + var dragScrollSpeed = 5; + var dragScrollPercentage = 12; // 容器边缘滚动区域大小,百分比值 var isDndElem = function(el) { return el && el.nodeName.toLowerCase() == 'li' && !!el.getAttribute('lay-id'); @@ -750,9 +752,9 @@ layui.define('component', function(exports) { if(isDndElem(this)){ this.draggable = true; scrollable = container.hasClass('layui-tabs-scroll'); - tabListSize = headerElem.prop('scrollWidth'); - containerSize = headerElem.outerWidth(); - minOffset = containerSize - tabListSize; + tabListScrollSize = headerElem.prop('scrollWidth'); + tabListOuterSize = headerElem.outerWidth(); + minOffset = tabListOuterSize - tabListScrollSize; } }); headerElem.on('dragstart' + namespace, function(e) { @@ -772,15 +774,15 @@ layui.define('component', function(exports) { // 拖拽边缘滚动 if (scrollable) { var containerRect = container[0].getBoundingClientRect(); - var isScrollLeft = e.clientX - containerRect.left < scrollThreshold; - var isScrollRight = containerRect.right - e.clientX < scrollThreshold; + var isScrollLeft = (e.clientX - containerRect.left) / containerRect.width * 100 < dragScrollPercentage; + var isScrollRight = (containerRect.right - e.clientX) / containerRect.width * 100 < dragScrollPercentage - if (!isScrollLeft && !isScrollRight) { + if (!(isScrollLeft || isScrollRight)) { return; } - var step = 35; - var rAFStep = 5; + var step = dragScrollLength; + var rAFStep = dragScrollSpeed; var newOffset = headerElem.data('left') || 0; var cb = function () { From 0928fbea84b40585ae7c2c71857c7dbd9b103a68 Mon Sep 17 00:00:00 2001 From: sight <26325820+Sight-wcg@users.noreply.github.com> Date: Sun, 22 Jun 2025 20:51:45 +0800 Subject: [PATCH 4/5] update --- src/modules/tabs.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/modules/tabs.js b/src/modules/tabs.js index 2c4c9db8..e030ad29 100644 --- a/src/modules/tabs.js +++ b/src/modules/tabs.js @@ -758,7 +758,9 @@ layui.define('component', function(exports) { } }); headerElem.on('dragstart' + namespace, function(e) { - e.originalEvent.dataTransfer.effectAllowed = 'move'; + if(e.originalEvent.dataTransfer){ + e.originalEvent.dataTransfer.effectAllowed = 'move'; + } draggedElem = $(e.target); that.change(draggedElem, false, true); }); @@ -769,7 +771,9 @@ layui.define('component', function(exports) { }); headerElem.on("dragover" + namespace, function (e) { e.preventDefault(); - e.originalEvent.dataTransfer.dropEffect = 'move'; + if(e.originalEvent.dataTransfer){ + e.originalEvent.dataTransfer.dropEffect = 'move'; + } // 拖拽边缘滚动 if (scrollable) { From b5f1e9838ba3c065084c0e70250500ed7af73a95 Mon Sep 17 00:00:00 2001 From: sight <26325820+Sight-wcg@users.noreply.github.com> Date: Sun, 22 Jun 2025 23:41:05 +0800 Subject: [PATCH 5/5] update --- src/modules/tabs.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/modules/tabs.js b/src/modules/tabs.js index e030ad29..473dcc2b 100644 --- a/src/modules/tabs.js +++ b/src/modules/tabs.js @@ -778,10 +778,10 @@ layui.define('component', function(exports) { // 拖拽边缘滚动 if (scrollable) { var containerRect = container[0].getBoundingClientRect(); - var isScrollLeft = (e.clientX - containerRect.left) / containerRect.width * 100 < dragScrollPercentage; - var isScrollRight = (containerRect.right - e.clientX) / containerRect.width * 100 < dragScrollPercentage + var isScrollStart = (e.clientX - containerRect.left) / containerRect.width * 100 < dragScrollPercentage; + var isScrollEnd = (containerRect.right - e.clientX) / containerRect.width * 100 < dragScrollPercentage - if (!(isScrollLeft || isScrollRight)) { + if (!(isScrollStart || isScrollEnd)) { return; } @@ -792,9 +792,9 @@ layui.define('component', function(exports) { var cb = function () { if (step > 0) { step -= rAFStep; - if (isScrollLeft) { + if (isScrollStart) { newOffset = newOffset + rAFStep; - } else if (isScrollRight) { + } else if (isScrollEnd) { newOffset = newOffset - rAFStep; } if (newOffset > maxOffset) {