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(); // 选项卡面板容器的 mapthis.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>