1 开发目标
实现如下网站标题栏 TAB 组件:
在点击"页面2"选项卡后,TAB 组件会切换对应的面板:
2 详细需求
网站标题栏 TAB 组件该组件需根据客户端提供的参数创建,具备动态构建 TAB 区域、选项卡切换及自定义内容显示等功能。
2.1 组件创建与初始化
类似于 echarts 等知名商业组件的创建与初始化方式,本组件需要根据客户端提供的参数 container 以及 para 进行创建和初始化。
container 是一个已存在的 DOM 元素(一般是 DIV),组件将在此元素内部构建 TAB 区域,包含选项卡以及 TAB 面板。
para 是本组件的配置参数,该对象应包含以下属性:
- title:标题字符串,将显示在选项卡区域的最右侧。
- titleFontProp:标题字符串的字体属性,包括字体大小、颜色等。
- titleOffset:标题字符串两边的空白宽度,单位为像素。
- height:选项卡的高度,单位为像素。
- tabItemActiveColor:选项卡标题激活时下边框的颜色。
- tabItemTitleFontProp:选项卡标题字符串的字体属性,包括字体大小、颜色等。
- tabItemTitleOffset:选项卡标题字符串两边的空白宽度,单位为像素。
- tabItems:一个包含选项卡对象的数组,每个对象应包含以下属性:
-
- id:选项卡的唯一标识。
-
- title:选项卡标题字符串。
2.2 TAB 区域构建
组件在初始化时,应在传入的 div 元素内部构建TAB区域。TAB 区域应包含标题栏和选项卡内容区。标题栏应显示 title 字符串和所有 TAB 选项卡标题。选项卡内容区应初始化为隐藏状态。
2.3 TAB 选项卡创建与切换
对于 tabs 数组中的每个对象,组件应创建一个对应的选项卡。每个选项卡应包含一个标题和一个面板 DIV。标题应显示 TAB 标题对象的 title 和 titleIcon 属性,并可根据 activeColor 属性改变下边框的颜色。对应的 TAB 面板在选项卡激活时应显示,否则隐藏。
初始状态下,应激活第一个 TAB 选项卡。客户端可以通过某种方式(如点击事件)切换激活的 TAB 选项卡。
2.4 自定义内容显示
客户端应能够获取到 TAB 选项卡面板容器,以便在其中添加自定义内容。组件提供了根据 id 获取选项卡面板容器的方法,使客户端能够方便地获取和操作选项卡面板内容。
3 代码实现
首先创建一个 neat_headertab.js 文件,该文件用于本组件的工具类、窗体部件基类以及各个实现类的代码构建。
在具体的业务代码编写之前,先实现一个工具类以及一些工具方法,方便后面调用:
class CommonUtil {
// 设置 DIV 中的文字为水平与垂直居中
static centerTextInDiv(container) {
container.style.display = 'flex';
container.style.textAlign = 'center';
container.style.justifyContent = 'center';
container.style.flexDirection = 'column';
}
}
该工具类中包含一个可以将 DIV 中的文字设置为水平与垂直居中的静态方法。
接下来,定义一个通用的显示窗体的基类:
class NeatBaseWid {
constructor(container, para) {
this.container = container; // 接收调用者传入的 DOM 元素(一般是 DIV)
this.para = para; // 保存调用者传入的 para 对象
}
}
然后开始定义选项卡类型:
class NeatHeaderTabItem extends NeatBaseWid {
static TITLE_OFFSET = "20px"; // 默认标题字符串两边的空白宽度
static TITLE_FONTSIZE = "18px"; // 默认标题字符串的字体大小
static TITLE_COLOR = "#000"; // 默认标题字符串字体颜色
static ACTIVE_COLOR = "#000"; // 默认选项卡标题激活时下边框的颜色
constructor(container, panelContainer, para) {
super(container, para);
this.panelContainer = panelContainer; // 面板容器,用于显示调用者定义的界面
this.render();
}
上面代码定义了 NeatHeaderTabItem 的一些默认属性,并且创建构造函数,该函数接收调用者传入的 DIV 容器,并且调用 render 方法。
render 方法如下:
render() {
this.container.innerHTML = ''; // 清空容器
this.container.style.cursor = 'pointer';
let titleOffset = this.para.titleOffset ?? NeatHeaderTabItem.TITLE_OFFSET;
this.container.style.paddingLeft = titleOffset;
this.container.style.paddingRight = titleOffset;
this.container.style.fontSize = (this.para.titleFontProp && this.para.titleFontProp.fontSize) ?? this.TITLE_FONTSIZE;
this.container.style.color = (this.para.titleFontProp && this.para.titleFontProp.color) ?? this.TITLE_COLOR;
this.container.innerText = this.para.title;
CommonUtil.centerTextInDiv(this.container);
// 点击选项卡的触发动作
let that = this;
this.container.onclick=function(){
that.para.onClickFunc.call(that.para.onClickObj,that);
}
}
该方法除了完成选项卡的渲染逻辑之外,还为选项卡添加点击事件,当点击选项卡时,会调用 para.onClickFunc 函数,并将选项卡对象的本体作为参数传递。
NeatHeaderTabItem 还提供了选项卡激活与选项卡非激活两个对外的接口:
// 选项卡激活
activate() {
this.container.style.borderBottom = '2px solid ' + this.para.activeColor ?? NeatHeaderTabItem.ACTIVE_COLOR;
this.panelContainer.style.display = 'block';
}
// 选项卡非激活
deactivate() {
this.container.style.borderBottom = '2px solid #ffffff'
this.panelContainer.style.display = 'none';
}
}
之后,开始定义 TAB 组件的主体类型:NeatHeaderTabWidget
class NeatHeaderTabWidget extends NeatBaseWid {
static TAB_HEIGHT = "50px"; // 默认选项卡的高度
static TITLE_OFFSET = "50px"; // 默认标题字符串两边的空白宽度
static TITLE_FONTSIZE = "20px"; // 默认标题字符串的字体大小
static TITLE_COLOR = "#000"; // 默认标题字符串字体颜色
constructor(container, para) {
super(container, para);
this.tabContainer = null; // 选项卡栏
this.panelContainer = null; // 面板容器
this.tabItems = []; // 选项卡集合
this.activeTabItem = null; // 当前激活的选项卡
this.tabPanelMap = new Map(); // 选项卡面板容器的 map
this.render();
}
上面代码同样定义了 NeatHeaderTabWidget 的一些默认属性,并且创建构造函数,该函数接收调用者传入的 DIV 容器,并且调用 render 方法。
render 方法如下:
render() {
// 清空容器
this.container.innerHTML = '';
// 创建选项卡栏
this.tabContainer = document.createElement('div');
this.tabContainer.style.width = '100%';
this.tabContainer.style.height = this.para.height ?? NeatHeaderTabWidget.TAB_HEIGHT;
this.tabContainer.style.display = 'flex';
this.container.appendChild(this.tabContainer);
// 创建面板容器
this.panelContainer = document.createElement('div');
this.panelContainer.style.width = '100%';
this.panelContainer.style.height = 'calc( 100% - ' + this.tabContainer.style.height + ' )';
this.container.appendChild(this.panelContainer);
// 创建标题栏
let titleContainer = document.createElement('div');
let titleOffset = this.para.titleOffset ?? NeatHeaderTabWidget.TITLE_OFFSET;
titleContainer.style.marginLeft = titleOffset;
titleContainer.style.marginRight = titleOffset;
titleContainer.style.fontSize = (this.para.titleFontProp && this.para.titleFontProp.fontSize) ?? NeatHeaderTabWidget.TITLE_FONTSIZE;
titleContainer.style.color = (this.para.titleFontProp && this.para.titleFontProp.color) ?? NeatHeaderTabWidget.TITLE_COLOR;
titleContainer.style.fontWeight = 'bold'; // 标题默认是加粗
titleContainer.style.letterSpacing = '4px'; // 文本间距
titleContainer.innerText = this.para.title;
CommonUtil.centerTextInDiv(titleContainer);
this.tabContainer.appendChild(titleContainer);
// 创建选项卡
this.para.tabItems.forEach(element => {
let tabItemContainer = document.createElement('div');
this.tabContainer.appendChild(tabItemContainer);
let panelContainer = document.createElement('div');
panelContainer.style.width = '100%';
panelContainer.style.height = '100%';
panelContainer.style.display = 'none';
this.panelContainer.appendChild(panelContainer);
element.activeColor = this.para.tabItemActiveColor;
element.titleFontProp = this.para.tabItemTitleFontProp;
element.titleOffset = this.para.tabItemTitleOffset;
element.onClickFunc = this.tabChange;
element.onClickObj = this;
let tabItem = new NeatHeaderTabItem(tabItemContainer, panelContainer, element);
this.tabItems.push(tabItem);
this.tabPanelMap.set(element.id,tabItem.panelContainer);
});
if (this.tabItems.length > 0) {
this.tabItems[0].activate();
this.activeTabItem = this.tabItems[0];
}
}
该方法完成了创建选项卡栏、创建面板容器、创建标题栏以及创建选项卡等核心渲染逻辑。
最后 NeatHeaderTabWidget 对外提供了选项卡切换以及根据 id 获取选项卡面板容器的方法:
// 选项卡切换
tabChange(tabItem) {
this.tabItems.forEach(element => {
element.deactivate();
});
tabItem.activate();
this.activeTabItem = tabItem;
}
// 根据 id 获取选项卡面板容器
getTabPanelFromId(id){
if(this.tabPanelMap.has(id)){
return this.tabPanelMap.get(id);
}else{
return null;
}
}
}
完成 TAB 组件的代码编写后,可以创建 neater_headertab.html 文件,调用 TAB 组件
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>header tab</title>
<style>
html {
height: 100%;
}
body {
margin: 0;
height: 100%;
}
</style>
</head>
<body>
<div id="divMain" style="height: 100%;width: 100%;"></div>
</body>
<script src="./neat_headertab.js"></script>
<script>
let para = {
"title": "网站标题",
"titleFontProp": {
"fontSize": "26px",
"color": "#ea6f5a",
},
"titleOffset": "100px",
"height": "50px",
"tabItemActiveColor": "#ea6f5a",
"tabItemTitleFontProp": {
"fontSize": "17px",
"color": "#333",
},
"tabItemTitleOffset": "20px",
"tabItems": [
{
"id": "tabItem1",
"title": "页面1",
},
{
"id": "tabItem2",
"title": "页面2",
},
{
"id": "tabItem3",
"title": "页面3",
},
{
"id": "tabItem4",
"title": "页面4",
},
]
}
let headerTabWidget = new NeatHeaderTabWidget(document.getElementById('divMain'), para);
let panel1 = headerTabWidget.getTabPanelFromId('tabItem1');
panel1.style.backgroundColor = '#ADD8E6';
panel1.innerText='页面1';
let panel2 = headerTabWidget.getTabPanelFromId('tabItem2');
panel2.style.backgroundColor = '#E1FFFF';
panel2.innerText='页面2';
</script>
</html>