feat: 新增 tabs 标签页组件

pull/2477/head
贤心 2025-01-20 10:25:36 +08:00
parent fa73499d58
commit 752301c271
4 changed files with 941 additions and 8 deletions

236
examples/tabs.html Normal file
View File

@ -0,0 +1,236 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>标签页组件 - Layui</title>
<link rel="stylesheet" href="../src/css/layui.css">
</head>
<body>
<div class="layui-container layui-padding-3">
<div class="layui-tabs layui-hide-v" id="demoTabs1" lay-options="{closable: true, headerMode:'scroll'}">
<ul class="layui-tabs-header">
<li lay-id="aaa" lay-unclosed="true" class="layui-this">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>
</ul>
<div class="layui-tabs-body">
<div class="layui-tabs-item layui-show">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>
</div>
<div class="layui-btn-container">
<button class="layui-btn" onclick="layui.tabs.change('demoTabs1', 'ccc')">切换到Tab3</button>
<button class="layui-btn" onclick="layui.tabs.change('demoTabs1', 1)">切换到:第 2 项</button>
<button class="layui-btn" onclick="layui.tabs.close('demoTabs1', 'ddd')">关闭Tab4</button>
<button class="layui-btn" onclick="layui.tabs.close('demoTabs1', 1)">关闭:第 2 项</button>
<button class="layui-btn" lay-on="add">添加 Tab</button>
</div>
<div class="layui-py">
方法渲染:
<hr class="layui-my">
<div id="demoTabs2"></div>
</div>
<div class="layui-py">卡片风格:</div>
<div class="layui-tabs layui-tabs-card" lay-options="{index: 1}">
<ul class="layui-tabs-header">
<li>标题1</li>
<li>标题2</li>
<li><a href="" target="_blank" class="layui-font-blue">跳转项</a></li>
<li class="layui-disabled" lay-unselect>禁选项</li>
<li>标题5</li>
<li>标题6</li>
</ul>
<div class="layui-tabs-body">
<div class="layui-tabs-item">内容-1</div>
<div class="layui-tabs-item">内容-2</div>
<div class="layui-tabs-item">内容-3</div>
<div class="layui-tabs-item">内容-4</div>
<div class="layui-tabs-item">内容-5</div>
<div class="layui-tabs-item">内容-6</div>
</div>
</div>
<div class="layui-py">卡片 + 边框:</div>
<div class="layui-tabs layui-tabs-card layui-panel layui-inline">
<ul class="layui-tabs-header layui-bg-tint">
<li class="layui-this">标题1</li>
<li>标题2</li>
<li>标题3</li>
<li>标题4</li>
<li>标题5</li>
<li>标题6</li>
</ul>
<div class="layui-tabs-body">
<div class="layui-tabs-item layui-show">
<div class="layui-form">
<select>
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
<option>6</option>
</select>
</div>
</div>
<div class="layui-tabs-item">2</div>
<div class="layui-tabs-item">3</div>
<div class="layui-tabs-item">4</div>
<div class="layui-tabs-item">5</div>
<div class="layui-tabs-item">6</div>
</div>
</div>
<div class="layui-py">HASH</div>
<div class="layui-tabs layui-hide-v" id="demoTabs-hash">
<ul class="layui-tabs-header">
<li lay-id="A1" class="layui-this"><a href="#A1">标题题题题题题1</a></li>
<li lay-id="A2"><a href="#A2">标题题题2</a></li>
<li lay-id="A3"><a href="#A3">标题3</a></li>
<li lay-id="A4"><a href="#A4">标题题题题题题题4</a></li>
<li lay-id="A5"><a href="#A5">标题5</a></li>
<li lay-id="A6"><a href="#A6">标题6</a></li>
<li lay-id="A7"><a href="#A7">标题7</a></li>
<li lay-id="A8"><a href="#A8">标题题题题题题题8</a></li>
</ul>
</div>
<div class="layui-py">标签嵌套:</div>
<div class="layui-tabs layui-tabs-card" lay-options="{headerMode:'normal'}">
<ul class="layui-tabs-header">
<li class="layui-this">标题1</li>
<li>标题2</li>
<li>标题3</li>
</ul>
<div class="layui-tabs-body" style="padding: 16px;">
<div class="layui-tabs-item layui-show">
<div class="layui-tabs" lay-options="{headerMode:'normal'}">
<ul class="layui-tabs-header">
<li class="layui-this">标题 1-1</li>
<li>标题 1-2</li>
</ul>
<div class="layui-tabs-body">
<div class="layui-tabs-item layui-show">1-1</div>
<div class="layui-tabs-item">1-2</div>
</div>
</div>
</div>
<div class="layui-tabs-item">
<div class="layui-tabs" lay-options="{headerMode:'normal'}">
<ul class="layui-tabs-header">
<li class="layui-this">标题 2-1</li>
<li>标题 2-2</li>
<li>标题 2-3</li>
</ul>
<div class="layui-tabs-body">
<div class="layui-tabs-item layui-show">2-1</div>
<div class="layui-tabs-item">2-2</div>
<div class="layui-tabs-item">2-3</div>
</div>
</div>
</div>
<div class="layui-tabs-item">3</div>
<div class="layui-tabs-item">4</div>
<div class="layui-tabs-item">5</div>
</div>
</div>
<div class="layui-py" id="demoTabs3">
给任意元素添加 Tab 功能:
<hr class="layui-my">
<style>
#demoTabsHeader .layui-btn.layui-this{border-color: #eee; color: #000; background: none;}
#demoTabsBody .test-item{display: none;}
</style>
<div class="layui-btn-container" id="demoTabsHeader">
<button class="layui-btn layui-this">标题 1</button>
<button class="layui-btn">标题 2</button>
<button class="layui-btn">标题 3</button>
</div>
<div class="layui-panel layui-padding-3" id="demoTabsBody">
<div class="test-item layui-show">内容 111</div>
<div class="test-item">内容 222</div>
<div class="test-item">内容 333</div>
</div>
</div>
</div>
<script src="../src/layui.js"></script>
<script>
layui.use(['tabs', 'form', 'util'], function(){
var tabs = layui.tabs
var util = layui.util;
// 事件
util.on({
add: function(){
var n = Math.random()*1000 | 0; // 演示标记
//添加标签
tabs.add('demoTabs1', {
title: 'New Tab '+ n, // 此处加 n 仅为演示区分,实际应用不需要
content: 'New Tab Content '+ n,
id: 'new-'+ n,
aaa: 'attr-'+ n, // 自定义属性,其中 aaa 可任意命名
// mode: 'curr',
done: function(params) {
console.log(params);
}
});
}
});
// 方法渲染
tabs.render({
elem: '#demoTabs2',
header: [
{ title: 'Tab1' },
{ title: 'Tab2' },
{ title: 'Tab3' }
],
body: [
{ content: 'Tab content 1' },
{ content: 'Tab content 2' },
{ content: 'Tab content 3' }
],
// index: 1, //初始选中项
// className: 'layui-tabs-card',
// closable: true
});
// HASH 初始定位
var hash = layui.hash();
tabs.change('demoTabs-hash', hash.href);
// 给任意元素绑定 Tab 功能
tabs.render({
elem: '#demoTabs3',
header: ['#demoTabsHeader', '>button'],
body: ['#demoTabsBody', '>.test-item'],
change: function(obj) {
console.log(obj);
}
});
});
</script>
</body>
</html>

View File

@ -513,20 +513,56 @@ a cite{font-style: normal; *cursor:pointer;}
.layui-col-space32>*{padding: 16px;}
/* 内边距 */
/*
*
*/
.layui-padding-1{padding: 4px !important;}
.layui-padding-2{padding: 8px !important;}
.layui-padding-3{padding: 16px !important;}
.layui-padding-4{padding: 32px !important;}
.layui-padding-5{padding: 48px !important;}
/* 外边距 */
/* 上下内边距 */
.layui-py-xxs{padding: 5px 0;}
.layui-py-xs{padding: 8px 0;}
.layui-py-sm{padding: 11px 0;}
.layui-py-md, .layui-py{padding: 16px 0;}
.layui-py-lg{padding: 24px 0;}
.layui-py-xl{padding: 32px 0;}
.layui-py-0{padding: 0;}
.layui-py-1{padding: 4px 0;}
.layui-py-2{padding: 8px 0;}
.layui-py-3{padding: 12px 0;}
.layui-py-4{padding: 16px 0;}
.layui-py-5{padding: 20px 0;}
.layui-py-5{padding: 24px 0;}
/*
*
*/
.layui-margin-1{margin: 4px !important;}
.layui-margin-2{margin: 8px !important;}
.layui-margin-3{margin: 16px !important;}
.layui-margin-4{margin: 32px !important;}
.layui-margin-5{margin: 48px !important;}
/* 上下外边距 */
.layui-my-xxs{margin: 5px 0;}
.layui-my-xs{margin: 8px 0;}
.layui-my-sm{margin: 11px 0;}
.layui-my-md, .layui-my{margin: 16px 0;}
.layui-my-lg{margin: 24px 0;}
.layui-my-xl{margin: 32px 0;}
.layui-my-0{margin: 0;}
.layui-my-1{margin: 4px 0;}
.layui-my-2{margin: 8px 0;}
.layui-my-3{margin: 12px 0;}
.layui-my-4{margin: 16px 0;}
.layui-my-5{margin: 20px 0;}
.layui-my-5{margin: 24px 0;}
/*
*
@ -1284,6 +1320,42 @@ body .layui-table-tips .layui-layer-content{background: none; padding: 0; box-sh
.layui-dropdown:before{content:""; position: absolute; width: 100%; height: 6px; left: 0; top: -6px;}
.layui-dropdown-shade{top: 0; left: 0; width: 100%; height: 100%; _height: expression(document.body.offsetHeight+"px"); position: fixed; _position: absolute; pointer-events: auto;}
/* Tabs 标签页 */
.layui-tabs{position: relative;}
.layui-tabs *{box-sizing: border-box;}
.layui-tabs.layui-hide-v{overflow: hidden;}
.layui-tabs-header{position: relative; left: 0; height: 40px; white-space: nowrap; font-size: 0; transition: all .16s; -webkit-transition: all .16s;}
.layui-tabs-header:after,
.layui-tabs-scroll:after{content: ""; position: absolute; left: 0; bottom: 0; z-index: 0; width: 100%; border-bottom: 1px solid #eee;}
.layui-tabs-header li{position: relative; display: inline-block; vertical-align: middle; line-height: 40px; padding: 0 16px; text-align: center; cursor: pointer; font-size: 14px; transition: all .16s; -webkit-transition: all .16s;}
.layui-tabs-header li:first-child{margin-left: 0;}
.layui-tabs-header li a{display: block; padding: 0 16px; margin: 0 -16px;}
.layui-tabs-header .layui-this{color: #16baaa;}
.layui-tabs-header .layui-this:after{content: ""; position: absolute; left:0; top: 0; z-index: 1; width: 100%; height: 100%; border-bottom: 3px solid #16baaa; box-sizing: border-box; pointer-events: none;}
.layui-tabs-header .layui-badge,
.layui-tabs-header .layui-badge-dot{left: 5px; top: -1px;}
.layui-tabs-scroll{position: relative; overflow: hidden; padding: 0 40px;}
.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-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;}
.layui-tabs-body{padding: 16px 0;}
.layui-tabs-item{display: none;}
/* tabs 卡片风格 */
.layui-tabs-card>.layui-tabs-header .layui-this{background-color: #fff;}
.layui-tabs-card>.layui-tabs-header .layui-this:after{border: 1px solid #eee; border-bottom-color: #fff; border-radius: 2px 2px 0 0;}
.layui-tabs-card>.layui-tabs-header li:first-child.layui-this:after{margin-left: -1px;}
.layui-tabs-card>.layui-tabs-header li:last-child.layui-this:after{margin-right: -1px;}
.layui-tabs-card.layui-panel>.layui-tabs-header .layui-this:after{border-top: 0; border-radius: 0;}
.layui-tabs-card.layui-panel>.layui-tabs-body{padding: 16px;}
/** 导航菜单 **/
.layui-nav{position: relative; padding: 0 15px; background-color: #2f363c; color: #fff; border-radius: 2px; font-size: 0; box-sizing: border-box;}
.layui-nav *{font-size: 14px;}

View File

@ -62,6 +62,7 @@
tree: 'tree', // 树结构
table: 'table', // 表格
treeTable: 'treeTable', // 树表
tabs: 'tabs', // 标签页
element: 'element', // 常用元素操作
rate: 'rate', // 评分组件
colorpicker: 'colorpicker', // 颜色选择器
@ -71,6 +72,7 @@
util: 'util', // 工具块
code: 'code', // 代码修饰器
jquery: 'jquery', // DOM 库(第三方)
component: 'component', // 组件构建器
all: 'all',
'layui.all': 'layui.all' // 聚合标识(功能性的,非真实模块)
@ -257,8 +259,8 @@
// currentStyle.getAttribute 参数为 camelCase 形式的字符串
Layui.prototype.getStyle = function(node, name){
var style = node.currentStyle ? node.currentStyle : win.getComputedStyle(node, null);
return style.getPropertyValue
? style.getPropertyValue(name)
return style.getPropertyValue
? style.getPropertyValue(name)
: style.getAttribute(name.replace(/-(\w)/g, function(_, c){ return c ? c.toUpperCase() : '';}));
};
@ -401,13 +403,14 @@
var data = {
path: [],
search: {},
hash: (hash.match(/[^#](#.*$)/) || [])[1] || ''
hash: (hash.match(/[^#](#.*$)/) || [])[1] || '',
href: ''
};
if(!/^#\//.test(hash)) return data; // 禁止非路由规范
if (!/^#/.test(hash)) return data; // 禁止非路由规范
hash = hash.replace(/^#\//, '');
data.href = '/' + hash;
hash = hash.replace(/^#/, '');
data.href = hash;
hash = hash.replace(/([^#])(#.*$)/, '$1').split('/') || [];
// 提取 Hash 结构

622
src/modules/tabs.js Normal file
View File

@ -0,0 +1,622 @@
/**
* tabs
* 标签页组件
*/
layui.define('component', function(exports) {
'use strict';
var $ = layui.$;
// 创建组件
var component = layui.component({
name: 'tabs', // 组件名
// 默认配置
config: {
elem: '.layui-tabs',
trigger: 'click', // 触发事件
headerMode: 'auto' // 标签头部的显示模式
},
CONST: {
ELEM: 'layui-tabs',
HEADER: 'layui-tabs-header',
CLOSE: 'layui-tabs-close',
BODY: 'layui-tabs-body',
ITEM: 'layui-tabs-item',
CARD: 'layui-tabs-card',
TRIGGER_NAME: 'LAY_TABS_ELEM_callback'
},
isRenderOnEvent: false,
// 渲染
render: function() {
var that = this;
var options = that.config;
// 标签页元素项
that.elemHeader = ['.'+ component.CONST.HEADER + ':eq(0)', '>li'];
that.elemBody = ['.'+ component.CONST.BODY + ':eq(0)', '>.'+ component.CONST.ITEM];
// 获取头部和内容元素
that.elemItem = function(){
var thisElem = that.thisElem || options.elem;
return {
header: {
elem: thisElem.find(that.elemHeader[0]),
items: thisElem.find(that.elemHeader.join(''))
},
body: {
elem: thisElem.find(that.elemBody[0]),
items: thisElem.find(that.elemBody.join(''))
}
};
};
// 如果传入 header 参数
if (layui.type(options.header) === 'array') {
if (options.header.length === 0) return;
// 是否为元素绑定
if (typeof options.header[0] === 'string') {
that.elemHeader = options.header.concat();
that.thisElem = $('body');
} else { // 方法传值渲染
that.elemView = $('<div class="layui-tabs"></div>');
if (options.className) that.elemView.addClass(options.className);
var elemHeader = $('<ul class="layui-tabs-header"></ul>');
var elemBody = $('<div class="layui-tabs-body"></div>');
// 生成标签项
layui.each(options.header, function(i, item){
var elemHeaderItem = that.renderHeaderItem(item);
elemHeader.append(elemHeaderItem);
});
layui.each(options.body, function(i, item){
var elemBodyItem = that.renderBodyItem(item);
elemBody.append(elemBodyItem);
});
that.elemView.append(elemHeader).append(elemBody);
options.elem.html(that.elemView);
}
} else {
that.renderClose(); // 初始化标签关闭结构
}
// 如果传入 body 参数
if (layui.type(options.body) === 'array') {
if (typeof options.body[0] === 'string') {
that.thisElem = $('body');
that.elemBody = options.body.concat();
}
}
// 初始选中项
var data = that.data();
if ('index' in options && data.index != options.index) {
that.change(that.findHeaderItem(options.index));
} else if (data.index === -1) {
that.change(that.findHeaderItem(0));
}
// 初始化滚动结构
that.roll('auto');
// 清除隐藏占位
if (options.elem.hasClass(component.CONST.CLASS_HIDEV)) {
options.elem.removeClass(component.CONST.CLASS_HIDEV);
}
// 回调
typeof options.ready === 'function' && options.ready(data);
layui.event.call(options.elem[0], component.CONST.MOD_NAME, 'ready('+ options.id +')', data); // 事件
},
// 事件
events: function() {
var that = this;
var options = that.config;
var elemItem = that.elemItem();
var TRIGGER_NAME = component.CONST.TRIGGER_NAME;
var thisElem = that.thisElem ? elemItem.header.elem : options.elem;
// 移除重复事件
if (thisElem.data(TRIGGER_NAME)) {
thisElem.off(options.trigger, thisElem.data(TRIGGER_NAME));
}
// 点击标签头部
var elemHeaderItem = that.thisElem ? that.elemHeader[1] : that.elemHeader.join('');
thisElem.data(TRIGGER_NAME, function(){
that.change($(this));
}).on(options.trigger, elemHeaderItem, thisElem.data(TRIGGER_NAME));
// resize 事件
if (!inner.onresize) {
var timer;
$(window).on('resize', function(){
clearTimeout(timer);
timer = setTimeout(function(){
layui.each(component.cache.id, function(key){
var that = component.getThis(key);
if(!that) return;
that.roll('init');
});
},50);
});
inner.onresize = true;
}
},
// 实例接口
inst: {},
// 扩展接口
exports: {
// 增加标签
add: function(id, obj) {
var that = component.getThis(id);
if(!that) return this;
that.add(obj);
},
// 关闭单个标签
close: function(id, index) {
var that = component.getThis(id);
if(!that) return this;
if(index === undefined) index = that.data().index; // index 若不传,则表示关闭当前标签
that.close(that.findHeaderItem(index));
},
// 关闭多个标签。若传 index则按 index 所在标签为事件执行关闭操作
closeMore: function(id, type, index) {
var that = component.getThis(id);
if(!that) return this;
that.closeMore(type, index);
},
// 切换标签
change: function(id, index) {
var that = component.getThis(id);
if(!that) return this;
that.change(that.findHeaderItem(index));
},
// 获取标签信息
data: function(id) {
var that = component.getThis(id);
return that ? that.data() : {};
},
// 获取标签指定头部项
headerItem: function(id, index) {
var that = component.getThis(id);
return that ? that.findHeaderItem(index) : this;
},
// 获取标签指定头部项
bodyItem: function(id, index) {
var that = component.getThis(id);
return that ? that.findBodyItem(index) : this;
},
// 调整视图结构
setView: function(id) {
var that = component.getThis(id);
if (!that) return this;
that.roll('auto');
}
}
});
var inner = {};
var Class = component.Class;
// 增加标签
Class.prototype.add = function(obj){
var that = this;
var options = that.config;
var elemItem = that.elemItem();
var newHeaderItem = that.renderHeaderItem(obj);
var newBodyItem = that.renderBodyItem(obj);
// 插入方式
if (obj.mode === 'curr') { // 在当前标签后插入
var data = that.data();
data.thisHeader.after(newHeaderItem);
data.thisBody.after(newBodyItem);
} else {
var mode = ({
prepend: 'prepend'
,append: 'append'
})[obj.mode || 'append'] || 'append';
elemItem.header.elem[mode](newHeaderItem);
elemItem.body.elem[mode](newBodyItem);
}
// 将插入项切换为当前标签
that.change(newHeaderItem);
// 回调
var params = that.data();
typeof obj.done === 'function' && obj.done(params);
typeof options.add === 'function' && options.add(params);
layui.event.call(newHeaderItem[0], component.CONST.MOD_NAME, 'add('+ options.id +')', params); // 事件
};
// 关闭指定标签
Class.prototype.close = function(thisHeader) {
if(!thisHeader || !thisHeader[0]) return;
var that = this;
var options = that.config;
var index = thisHeader.index();
if (!thisHeader[0]) return;
// 不可关闭项
if (thisHeader.attr('lay-unclosed')) return;
// 如果关闭的是当前标签,则更换当前标签下标
if (thisHeader.hasClass(component.CONST.CLASS_THIS)) {
if (thisHeader.next()[0]) {
that.change(thisHeader.next());
} else if(thisHeader.prev()[0]) {
that.change(thisHeader.prev());
}
}
// 移除元素
thisHeader.remove();
that.findBodyItem(index).remove();
that.roll('auto', index);
// 回调
var params = that.data();
typeof options.close === 'function' && options.close(params);
layui.event.call(params.thisHeader[0], component.CONST.MOD_NAME, 'close('+ options.id +')', params); // 事件
};
// 批量关闭标签
Class.prototype.closeMore = function(type, index) {
var that = this;
var options = that.config;
var elemItem = that.elemItem();
var data = that.data();
var headers = elemItem.header.items;
var bodys = elemItem.body.items;
var FILTER = ':not([lay-unclosed])';
index = index === undefined ? data.index : index;
// 遍历
headers.each(function(i){
var othis = $(this);
var unclosed = othis.attr('lay-unclosed');
// 标注不可关闭项
if (unclosed) {
bodys.eq(i).attr('lay-unclosed', unclosed);
}
});
// 移交当前标签项
if (!data.thisHeader.attr('lay-unclosed')) {
if(type === 'all' || !type){
var nextHeader = headers.filter(':gt('+ data.index +')[lay-unclosed]').eq(0);
var prevHeader = $(headers.filter(':lt('+ data.index +')[lay-unclosed]').get().reverse()).eq(0);
if (nextHeader[0]) {
that.change(nextHeader);
} else if(prevHeader[0]) {
that.change(prevHeader);
}
} else if(index !== data.index) {
that.change(that.findHeaderItem(index));
}
}
// 执行批量关闭标签
if (type === 'other') { // 关闭其他标签
headers.eq(index).siblings(FILTER).remove();
bodys.eq(index).siblings(FILTER).remove();
} else if(type === 'right') { // 关闭右侧标签
headers.filter(':gt('+ index +')'+ FILTER).remove();
bodys.filter(':gt('+ index +')'+ FILTER).remove();
} else { // 关闭所有标签
headers.filter(FILTER).remove();
bodys.filter(FILTER).remove();
}
// 回调
var params = that.data();
typeof options.close === 'function' && options.close(params);
layui.event.call(params.thisHeader[0], component.CONST.MOD_NAME, 'close('+ options.id +')', params); // 事件
};
// 切换标签
Class.prototype.change = function(thisHeader) {
if (!thisHeader || !thisHeader[0]) return;
var that = this;
var options = that.config;
var index = thisHeader.index();
var thatA = thisHeader.find('a');
var isLink = typeof thatA.attr('href') === 'string' && thatA.attr('target') === '_blank'; // 是否存在跳转链接
var unselect = typeof thisHeader.attr('lay-unselect') === 'string'; // 是否禁用选中
// 执行切换
if (!(isLink || unselect)) {
// 头部
thisHeader.addClass(component.CONST.CLASS_THIS).siblings()
.removeClass(component.CONST.CLASS_THIS);
// 内容
that.findBodyItem(index).addClass(component.CONST.CLASS_SHOW)
.siblings().removeClass(component.CONST.CLASS_SHOW);
}
that.roll('auto', index);
// 回调
var params = that.data();
typeof options.change === 'function' && options.change(params);
layui.event.call(thisHeader[0], component.CONST.MOD_NAME, 'change('+ options.id +')', params); // 事件
};
// 渲染标签头部项
Class.prototype.renderHeaderItem = function(obj){
var that = this;
var options = that.config;
var elemItem = $(obj.headerItem || options.headerItem || '<li></li>');
elemItem.html(obj.title || 'New Tab');
// 追加属性
layui.each(obj, function(key, value){
if(/^(title|content|mode|done)$/.test(key)) return;
elemItem.attr('lay-'+ key, value);
});
// 追加标签关闭元素
that.appendClose(elemItem, obj);
return elemItem;
};
// 渲染标签内容项
Class.prototype.renderBodyItem = function(obj) {
var that = this
var options = that.config
var elemItem = $(obj.bodyItem || options.bodyItem || '<div class="'+ component.CONST.ITEM +'"></div>');
elemItem.html(obj.content || '');
return elemItem;
};
// 给某一个标签项追加可关闭元素
Class.prototype.appendClose = function(othis, obj) {
var that = this
var options = that.config;
if(!options.closable) return;
obj = obj || {};
if (obj.unclosed || othis.attr('lay-unclosed')) return; // 不可关闭项
if (!othis.find('.'+ component.CONST.CLOSE)[0]) {
var close = $('<i class="layui-icon layui-icon-close layui-unselect '+ component.CONST.CLOSE +'"></i>');
close.on('click', function(){
that.close($(this).parent());
return false;
});
othis.append(close);
}
};
// 渲染标签可关闭元素
Class.prototype.renderClose = function(othis) {
var that = this;
var options = that.config;
var elemItem = that.elemItem();
var hasDel = that.cache('close');
// 是否开启关闭
if (options.closable) {
if (!hasDel) {
elemItem.header.items.each(function(){
that.appendClose($(this));
});
that.cache('close', true);
}
} else if(hasDel) {
elemItem.header.items.each(function() {
$(this).find('.'+ component.CONST.CLOSE).remove();
});
}
};
// 滚动标签
Class.prototype.roll = function(type, index) {
var that = this;
var options = that.config;
var elemItem = that.elemItem();
var headerElem = elemItem.header.elem;
var headerItems = elemItem.header.items;
var scrollWidth = headerElem.prop('scrollWidth'); // 实际总长度
var outerWidth = Math.ceil(headerElem.outerWidth()); // 可视区域的长度
var tabsLeft = headerElem.data('left') || 0;
var scrollMode = options.headerMode === 'scroll'; // 标签头部是否始终保持滚动模式
// 让选中标签始终保持在可视区域
var rollToVisibleArea = function() {
index = isNaN(index) ? that.data().index : index;
var thisItemElem = headerItems.eq(index);
if (!thisItemElem[0]) return;
// 当前标签的相对水平坐标值
var thisLeft = Math.ceil(thisItemElem.position().left);
var padding = 1; // 让边界额外保持一定间距
// 当选中标签溢出在可视区域「左侧」时
var countWidth = thisLeft - (thisItemElem.prev().outerWidth() || 0); // 始终空出上一个标签
if (countWidth > 0) countWidth = countWidth - padding;
// 左侧临界值
if (tabsLeft + countWidth < 0) {
tabsLeft = countWidth >= 0 ? countWidth : 0; // 标签的复原位移不能超出 0
return headerElem.css('left', -tabsLeft).data('left', -tabsLeft);;
}
// 当选中标签溢出在可视区域「右侧」时,
var countWidth = thisLeft + thisItemElem.outerWidth()
+ (thisItemElem.next().outerWidth() || 0) + padding; // 始终空出下一个标签
// 右侧临界值
if (tabsLeft + countWidth - outerWidth > 0) {
tabsLeft = countWidth - outerWidth;
headerElem.css('left', -tabsLeft).data('left', -tabsLeft);
}
};
// css 类名
var CLASS_SCROLL = 'layui-tabs-scroll';
var CLASS_BAR = 'layui-tabs-bar';
var CLASS_BAR_ICON = ['layui-icon-prev', 'layui-icon-next'];
// 滚动结构
var rollElem = {
elem: $('<div class="'+ CLASS_SCROLL +' layui-unselect"></div>'),
bar: $([
'<div class="'+ CLASS_BAR +'">',
'<i class="layui-icon '+ CLASS_BAR_ICON[0] +'" lay-mode="prev"></i>',
'<i class="layui-icon '+ CLASS_BAR_ICON[1] +'" lay-mode="next"></i>',
'</div>'
].join(''))
};
// 不渲染头部滚动结构
if (options.headerMode === 'normal') return;
// 是否渲染滚动结构
var elemScroll = headerElem.parent('.'+ CLASS_SCROLL);
if (scrollMode || (!scrollMode && scrollWidth > outerWidth)) {
if (!elemScroll[0]) {
if (options.elem.hasClass(component.CONST.CARD)) {
rollElem.elem.addClass(component.CONST.CARD);
}
headerElem.wrap(rollElem.elem);
headerElem.after(rollElem.bar);
// 点击左右箭头
rollElem.bar.children().on('click', function(){
var othis = $(this);
var mode = othis.attr('lay-mode');
if ($(this).hasClass(component.CONST.CLASS_DISABLED)) return;
mode && that.roll(mode);
});
}
} else if(!scrollMode) {
if (elemScroll[0]) {
elemScroll.find('.'+ CLASS_BAR).remove();
headerElem.unwrap().css('left', 0).data('left', 0);
} else {
return;
}
}
if (type === 'init') return;
// 重新获取
scrollWidth = headerElem.prop('scrollWidth') // 实际总长度
outerWidth = headerElem.outerWidth() // 可视区域的长度
elemScroll = headerElem.parent('.'+ CLASS_SCROLL);
// 左箭头(往右滚动)
if (type === 'prev') {
// 当前的 left 减去可视宽度,用于与上一轮的页签比较
var prevLeft = -tabsLeft - outerWidth;
if(prevLeft < 0) prevLeft = 0;
headerItems.each(function(i, item){
var li = $(item);
var left = Math.ceil(li.position().left);
if (left >= prevLeft) {
headerElem.css('left', -left).data('left', -left);
return false;
}
});
} else if(type === 'auto') { // 自动识别滚动
rollToVisibleArea();
} else { // 右箭头(往左滚动)
headerItems.each(function(i, item){
var li = $(item);
var left = Math.ceil(li.position().left);
if (left + li.outerWidth() >= outerWidth - tabsLeft) {
headerElem.css('left', -left).data('left', -left);
return false;
}
});
}
// 同步箭头状态
tabsLeft = headerElem.data('left') || 0;
// 左
elemScroll.find('.'+ CLASS_BAR_ICON[0])[
tabsLeft < 0 ? 'removeClass' : 'addClass'
](component.CONST.CLASS_DISABLED);
// 右
elemScroll.find('.'+ CLASS_BAR_ICON[1])[
parseFloat(tabsLeft + scrollWidth) - outerWidth > 0
? 'removeClass'
: 'addClass'
](component.CONST.CLASS_DISABLED);
};
// 根据 id 或 index 获取相关标签头部项
Class.prototype.findHeaderItem = function(index) {
if(!(
typeof index === 'number'
|| (typeof index === 'string' && index)
)) return;
var headerItems = this.elemItem().header.items;
var item = headerItems.filter('[lay-id="'+ index +'"]');
return item[0] ? item : headerItems.eq(index);
};
// 根据 index 获取相关标签内容项
Class.prototype.findBodyItem = function(index) {
return this.elemItem().body.items.eq(index);
};
// 返回给回调的公共信息
Class.prototype.data = function() {
var that = this;
var options = that.config;
var elemItem = that.elemItem();
var thisHeader = elemItem.header.items.filter('.'+ component.CONST.CLASS_THIS);
var index = thisHeader.index();
return {
options: options, // 标签配置信息
elemItem: elemItem,
thisHeader: thisHeader, // 当前标签头部项
thisBody: that.findBodyItem(index), // 当前标签内容项
index: index, // 当前标签下标
length: elemItem.header.items.length // 当前标签数
}
};
// 初始化渲染
$(function() {
component.render();
});
exports(component.CONST.MOD_NAME, component);
});