pull/2735/merge
morning-star 2025-06-22 15:42:16 +00:00 committed by GitHub
commit c379e51719
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 174 additions and 16 deletions

View File

@ -99,6 +99,20 @@
`false`
</td>
</tr>
<tr>
<td>dragSort <sup>2.12+</sup></td>
<td>
是否开启标签项拖拽排序功能
</td>
<td>boolean</td>
<td>
`false`
</td>
</tr>
<tr>

View File

@ -357,6 +357,19 @@ tabs.on('afterClose(testID)', function(data) {
});
```
<h3 id="on-dragend" class="ws-anchor ws-bold">标签拖拽结束后的事件 <sup>2.12+</sup></h3>
`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 组件。

View File

@ -9,22 +9,22 @@
<body>
<div class="layui-container layui-padding-3 layui-text">
<h2>动态操作</h2>
<div class="layui-tabs layui-hide-v" id="demoTabs1" lay-options="{closable: true, headerMode:'scroll'}">
<div class="layui-tabs layui-hide-v" id="demoTabs1" lay-options="{closable: true, headerMode:'scroll', dragSort: true}">
<ul class="layui-tabs-header">
<li lay-id="aaa" lay-closable="false">Tab1</li>
<li lay-id="bbb">Tab2</li>
<li lay-id="ccc">Tab3</li>
<li lay-id="ddd">Tab4</li>
<li lay-id="eee">Tab5</li>
<li lay-id="fff">Tab6</li>
<li lay-id="t-1" lay-closable="false">Tab1</li>
<li lay-id="t-2">Tab2</li>
<li lay-id="t-3">Tab3</li>
<li lay-id="t-4">Tab4</li>
<li lay-id="t-5">Tab5</li>
<li lay-id="t-6">Tab6</li>
</ul>
<div class="layui-tabs-body">
<div class="layui-tabs-item">Tab Content-1</div>
<div class="layui-tabs-item">Tab Content-2</div>
<div class="layui-tabs-item">Tab Content-3</div>
<div class="layui-tabs-item">Tab Content-4</div>
<div class="layui-tabs-item">Tab Content-5</div>
<div class="layui-tabs-item">Tab Content-6</div>
<div lay-id="t-1" class="layui-tabs-item">Tab Content-1</div>
<div lay-id="t-2" class="layui-tabs-item">Tab Content-2</div>
<div lay-id="t-3" class="layui-tabs-item">Tab Content-3</div>
<div lay-id="t-4" class="layui-tabs-item">Tab Content-4</div>
<div lay-id="t-5" class="layui-tabs-item">Tab Content-5</div>
<div lay-id="t-6" class="layui-tabs-item">Tab Content-6</div>
</div>
</div>

View File

@ -1316,6 +1316,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;}

View File

@ -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);
// 标签切换
if (!disabledScroll) {
that.roll('auto', index);
}
// 重新获取标签相关数据
var data = that.data();
@ -714,6 +725,124 @@ 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 draggedElem;
var scrollable;
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');
};
headerElem.off(namespace);
headerElem.on('mousedown' + namespace, '>li', function() {
if(isDndElem(this)){
this.draggable = true;
scrollable = container.hasClass('layui-tabs-scroll');
tabListScrollSize = headerElem.prop('scrollWidth');
tabListOuterSize = headerElem.outerWidth();
minOffset = tabListOuterSize - tabListScrollSize;
}
});
headerElem.on('dragstart' + namespace, function(e) {
if(e.originalEvent.dataTransfer){
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();
if(e.originalEvent.dataTransfer){
e.originalEvent.dataTransfer.dropEffect = 'move';
}
// 拖拽边缘滚动
if (scrollable) {
var containerRect = container[0].getBoundingClientRect();
var isScrollStart = (e.clientX - containerRect.left) / containerRect.width * 100 < dragScrollPercentage;
var isScrollEnd = (containerRect.right - e.clientX) / containerRect.width * 100 < dragScrollPercentage
if (!(isScrollStart || isScrollEnd)) {
return;
}
var step = dragScrollLength;
var rAFStep = dragScrollSpeed;
var newOffset = headerElem.data('left') || 0;
var cb = function () {
if (step > 0) {
step -= rAFStep;
if (isScrollStart) {
newOffset = newOffset + rAFStep;
} else if (isScrollEnd) {
newOffset = newOffset - rAFStep;
}
if (newOffset > maxOffset) {
newOffset = maxOffset;
} else if (newOffset < minOffset) {
newOffset = minOffset;
}
headerElem.css('left', newOffset).data('left', newOffset);
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, {
/**