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] =?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, {
/**