昨天做的tabs窗口很满意。今天乘胜追击,让它根据自己的尺寸改变显示样式,宽度太小了,标签页可以浮动到它的一边。具体效果:左右总是喜欢简单明了的东西,所以想简单点,但是现实不允许,功能有点复杂。硬着头皮搞了一个下午,终于搞定了。左右窗口应该使用相同的控件,尽可能增加代码的复用性。控件有多种状态:normaldisplay(普通tabs窗口)、list(显示图标和标题,点击时弹出tab页)、mini-list(只显示图标,弹出tab页)单击时)。当控件在界面左侧时,标签页在右侧弹出。反之,当控件在界面右侧时,标签页弹出在左侧。从普通选项卡,当缩小到列表显示时,所有选项卡都处于非活动状态。要从列表放大到普通选项卡,默认情况下会选择一个选项卡。有这么多状态需求,代码很容易变得混乱。幸运的是,有一种叫做“状态模式”的设计模式,可以很好的解决这个问题。缺点是初始代码量稍大,优点是方便后期管理。昨天做了两个tabs控件,一个是WidgetTabs,一个是PageTabs。后者现在还能满足我们的需求,只需要修改WidgetTabs即可。删掉昨天实现的一些代码,先重写模板,脚本代码按照模板写,可以让脚本代码更加实用,就像在测试驱动开发中一样,先写测试再写是一个道理代码。还有,差点忘了。昨天的代码中,所有的style样式都放在了style.css文件中,然后全局引入vue。随着我们写的控件越来越多,这个文件会越来越臃肿,不方便管理。这次将WidgetTabs相关的样式代码获取到vue组件中。先看模板代码:`{{tab.name}}{{selectedTab?selectedTab.name:''}}
×`最上面的DIV是我们的控件Shell,class对应三个css类三种状态:1.默认状态,空字符串2.列表状态,中间-size3.mini-list状态,mini-sizecss代码根据这个csss类,使用不同的方式来展示它的子元素,从而实现正常显示,或者弹出显示两种样式。ref相当于给这个DIV设置了一个唯一的ID。我们在代码中可以通过这个ID获取对应的dom元素,从而判断当前控件的大小,并根据这个大小调整控件的显示样式。ul元素显示选项卡控件的导航标签部分。它根据每个标签页的显示或隐藏来判断标签是否被激活。它还具有接收鼠标点击事件并将其传递给控制脚本的功能。模板基本没什么逻辑,主要是Display和接收事件。是否显示图标,根据showIcon计算属性决定。是否显示标题,根据showTitle计算属性决定。是否显示整个tabbody是根据showTabBody计算属性来决定的。因为tabbody有时停靠在控件的左侧,有时停靠在控件的右侧,所以这种停靠方式是根据属性dockLeft来确定的,如果停靠在左侧dockLeft为true,否则为true是假的。tabTitle为停靠时显示的标题区域:根据计算出的属性showTabTitle决定是否显示。关闭按钮负责接收点击事件并将其传递给控制器??脚本。不管怎样实现,控制脚本只需要满足模板的这个要求即可。相当于确定了接口,根据接口设计实现方法。确定使用状态模式来实现,根据状态设计三个状态类:NormalState(普通tabs控件)、MiddleState(列表状态,有标题和图标)、MiniState(迷你列表状态,只显示图标).后两个类有一些共同的操作,比如弹出隐藏标签等,可以继承一个共同的基类:ListState。三个状态类在功能上也有一些交集,它们可以有一个共同的基类State。类关系图如下(多年没用过UML工具,用Excel凑合):不仔细看根本不知道这张图是由卓越。我以为它是由一些高端UML工具制作的。状态类对应的代码:`classState{constructor(context){this.context=context}widthChange(width){if(width<=90){this.toState(this.context.miniState)}elseif(width<=160){this.toState(this.context.middleState)}else{this.toState(this.context.normalState)}}showTabBody(){返回真}showTabTitle(){返回假}showIcon(){返回假}showTitle(){返回真}close(){}toState(state){if(this.context.state!==state){if(this.context.state===this.context.normalState){this.context.selectedTab.isShow=falseconsole.log('dddd')}if(state===this.context.normalState){this.context.selectedTab.isShow=true}this.context.state=state}}stateClass(){return''}}classNormalStateextendsState{constructor(context){super(context)}clickTab(clickedTab){this.context.tabs.forEach(tab=>{tab.isShow\=(tab.name==clickedTab.name)this.context.selectedTab=clickedTab});}}//需要弹出式显示标签内容classListStateextendsState{constructor(context){super(context)}showTabBody(){返回this.context.selectedTab.isShow}showTabTitle(){返回true}showIcon(){返回true}showTitle(){返回true}close(){this.context。selectedTab.isShow=false}clickTab(clickedTab){this.context.tabs.forEach(tab=>{if(tab===clickedTab){tab.isShow\=!tab.isShowthis.context.selectedTab=clickedTab}else{tab.isShow\=false}});}}//应该状态显示图标签跟标题classMiddleStateextendsListState{constructor(context){super(context)}stateClass(){return'middle-size'}}//该状态只显示图标classMiniStateextendsListState{constructor(context){super(context)}showTitle(){returnfalse}stateClass(){return'mini-size'}}`控制脚本代码:`exportdefault{name:'WidgetTabs',data(){return{tabs:\[\],state:null,selectedTab:null,dockLeft:false,}},created(){this.tabs=this.$children;this.normalState=newNormalState(this)this.middleState=newMiddleState(this)this.miniState=newMiniState(this)this.state=this.normalState},计算:{stateClass(){returnthis.state.stateClass()},showIcon(){returnthis.state.showIcon()},showTitle(){returnthis.state.showTitle()},showTabBody(){返回this.state.showTabBody()},showTabTitle(){返回this.state.showTabTitle()},},方法:{click(clickTab){this.state.clickTab(clickTab)},mouseMove(){if(this.$refs.widget){this.dockLeft=this.$refs.widget.offsetLeft<50this.state.widthChange(this.$refs.widget.offsetWidth)}},mouseDown(事件){document.addEventListener('mousemove',this.mouseMove)},mouseUp(事件){document.removeEventListener('mousemove',this.mouseMove)},close(){this.state.close()}},mounted(){document.addEventListener('mousedown',this.mouseDown)document.addEventListener('mouseup',this.mouseUp)this.tabs.forEach(tab=>{if(tab.isShow){this.selectedTab=tab}});},beforeDestroyed(){document.removeEventListener('mousedown',this.mouseDown)document.removeEventListener('mouseup',this.mouseUp)},}`在创建组件时初始化各种状态需要注意的一点是,需要在窗口变化时动态获取控件的宽度,以确定控件处于哪种状态。JS中DIV没有resize事件,可以用鼠标事件代替。我们的窗口大小是通过鼠标拖动来实现的,所以跟踪鼠标拖动事件,动态查询控件的大小,然后分发事件。至此控制完成。写这篇文章的事件比写代码要花更长的时间。我天生就是程序员,不是作家。整个项目的历史节点代码,请到我的Github查看:https://github.com/vularsoft/...如何找到历史节点:RXEditor是一款Boostrap代码的可视化编辑工具。本系列记录如果对本软件的开发过程有任何疑问,欢迎在ithub上给我留言。