1- import { computed , defineComponent , provide , reactive } from 'vue'
1+ import {
2+ defineComponent ,
3+ onBeforeMount ,
4+ onMounted ,
5+ onUpdated ,
6+ PropType ,
7+ provide ,
8+ reactive ,
9+ ref
10+ } from 'vue' ;
211import './tabs.scss' ;
312
413export type Active = string | number | null ;
5- export type TabsType = 'tabs' | 'pills' | 'options' | 'wrapped' | 'slider'
14+ export type TabsType = 'tabs' | 'pills' | 'options' | 'wrapped' | 'slider' ;
615export interface Tabs {
716 state : TabsState
817}
@@ -18,7 +27,7 @@ export default defineComponent({
1827 type : [ String , Number ] ,
1928 default : null
2029 } ,
21- // TODO:其中 slider 类型还没有实现
30+
2231 type : {
2332 type : String as ( ) => TabsType ,
2433 default : 'tabs'
@@ -42,51 +51,149 @@ export default defineComponent({
4251 cssClass : {
4352 type : String ,
4453 default : ''
54+ } ,
55+ beforeChange : {
56+ type : Function as PropType < ( id : Active ) => boolean > ,
57+ default : null
4558 }
4659 } ,
47- // TODO: beforeChange没有完成实现
48- emits : [ 'update:modelValue' , 'activeTabChange' , 'beforeChange' ] ,
60+
61+ emits : [ 'update:modelValue' , 'activeTabChange' ] ,
4962 setup ( props , { emit, slots } ) {
50- const active = computed ( ( ) => {
51- return props . modelValue
52- } )
63+ const tabsEle = ref ( null ) ;
64+ const data = reactive ( { offsetLeft : 0 , offsetWidth : 0 , id : null } ) ;
5365 const state : TabsState = reactive ( {
5466 data : [ ] ,
55- active,
67+ active : props . modelValue ,
5668 showContent : props . showContent
5769 } ) ;
5870 provide < Tabs > ( 'tabs' , {
5971 state
6072 } ) ;
61- function activateTab ( tab : Active ) {
62- emit ( 'beforeChange' ) ;
63- emit ( 'update:modelValue' , tab ) ;
64- if ( props . reactivable ) {
65- emit ( 'activeTabChange' , tab )
73+
74+ const canChange = function ( currentTab : Active ) {
75+ let changeResult = Promise . resolve ( true ) ;
76+ if ( typeof props . beforeChange === 'function' ) {
77+ const result : any = props . beforeChange ( currentTab ) ;
78+ if ( typeof result !== 'undefined' ) {
79+ if ( result . then ) {
80+ changeResult = result ;
81+ } else {
82+ console . log ( result ) ;
83+ changeResult = Promise . resolve ( result ) ;
84+ }
85+ }
6686 }
67- }
6887
88+ return changeResult ;
89+ } ;
90+ const activeClick = function ( item , tabEl ?) {
91+ if ( ! props . reactivable && props . modelValue === item . id ) {
92+ return ;
93+ }
94+ canChange ( item . id ) . then ( ( change ) => {
95+ if ( ! change ) {
96+ return ;
97+ }
98+ const tab = state . data . find ( ( itemOption ) => itemOption . id === item . id ) ;
99+ if ( tab && ! tab . disabled ) {
100+ emit ( 'update:modelValue' , tab . id ) ;
101+ if ( props . type === 'slider' && tabEl && tabsEle ) {
102+ this . offsetLeft =
103+ tabEl . getBoundingClientRect ( ) . left -
104+ this . tabsEle . nativeElement . getBoundingClientRect ( ) . left ;
105+ this . offsetWidth = tabEl . getBoundingClientRect ( ) . width ;
106+ }
107+ emit ( 'activeTabChange' , tab . id ) ;
108+ }
109+ } ) ;
110+ } ;
69111 const ulClass : string [ ] = [ props . type ] ;
70112 props . cssClass && ulClass . push ( props . cssClass ) ;
71- props . vertical && ulClass . push ( 'devui-nav-stacked' )
72- return ( ) => {
73- return < div >
74- < ul role = "tablist" class = { `devui-nav devui-nav-${ ulClass . join ( ' ' ) } ` } id = "devuiTabs11" >
75- {
76- state . data . map ( ( item ) => {
77- return < li role = "presentation" onClick = { ( ) => activateTab ( ( item . id || item . tabId ) ) } class = { active . value === ( item . id || item . tabId ) ? 'active' : '' } id = { item . id || item . tabId } >
78- < a role = "tab" data-toggle = { item . id } aria-expanded = { active . value === ( item . id || item . tabId ) } >
79- < span > { item . title } </ span >
80- </ a >
81- </ li >
82- } )
113+ props . vertical && ulClass . push ( 'devui-nav-stacked' ) ;
114+ onUpdated ( ( ) => {
115+ if ( props . type === 'slider' ) {
116+ // 延时等待active样式切换至正确的tab
117+ setTimeout ( ( ) => {
118+ const tabEle = tabsEle . value . querySelector (
119+ '#' + props . modelValue + '.active'
120+ ) ;
121+ if ( tabEle ) {
122+ data . offsetLeft =
123+ tabEle . getBoundingClientRect ( ) . left -
124+ tabsEle . value . getBoundingClientRect ( ) . left ;
125+ data . offsetWidth = tabEle . getBoundingClientRect ( ) . width ;
83126 }
84- < div class = { `devui-nav-${ props . type } -animation` } > </ div >
85- </ ul >
86- { slots . default ( ) }
87- </ div >
88-
89- }
127+ } ) ;
128+ }
129+ } ) ;
130+ onBeforeMount ( ( ) => {
131+ if (
132+ props . type !== 'slider' &&
133+ props . modelValue === undefined &&
134+ state . data . length > 0
135+ ) {
136+ activeClick ( state . data [ 0 ] ) ;
137+ }
138+ } ) ;
139+ onMounted ( ( ) => {
140+ if (
141+ props . type === 'slider' &&
142+ props . modelValue === undefined &&
143+ state . data . length > 0 &&
144+ state . data [ 0 ]
145+ ) {
146+ activeClick (
147+ state . data [ 0 ] . tabsEle . value . getElementById ( state . data [ 0 ] . tabId )
148+ ) ;
149+ }
150+ } ) ;
151+ return ( ) => {
152+ return (
153+ < div >
154+ < ul
155+ ref = { tabsEle }
156+ role = 'tablist'
157+ class = { `devui-nav devui-nav-${ ulClass . join ( ' ' ) } ` }
158+ id = 'devuiTabs11'
159+ >
160+ { state . data . map ( ( item ) => {
161+ return (
162+ < li
163+ role = 'presentation'
164+ onClick = { ( ) => {
165+ activeClick ( item ) ;
166+ } }
167+ class = {
168+ ( props . modelValue === ( item . id || item . tabId )
169+ ? 'active'
170+ : '' ) +
171+ ' ' +
172+ ( item . disabled ? 'disabled' : '' )
173+ }
174+ id = { item . id || item . tabId }
175+ >
176+ < a
177+ role = 'tab'
178+ data-toggle = { item . id }
179+ aria-expanded = { props . modelValue === ( item . id || item . tabId ) }
180+ >
181+ < span > { item . title } </ span >
182+ </ a >
183+ </ li >
184+ ) ;
185+ } ) }
186+ < div
187+ class = { `devui-nav-${ props . type } -animation` }
188+ style = { {
189+ left : data . offsetLeft + 'px' ,
190+ width : data . offsetWidth + 'px'
191+ } }
192+ > </ div >
193+ </ ul >
194+ { slots . default ( ) }
195+ </ div >
196+ ) ;
197+ } ;
90198 }
91- } )
92-
199+ } ) ;
0 commit comments