实现Tabs组件

131 阅读1分钟

未来我们在使用时:

<k-tabs v-model="activeTab">
    <k-tab id="tab1" title="Tab1">Tab1 Content</k-tab>
    <k-tab id="tab2" title="Tab1">Tab2 Content</k-tab>
    <k-tab id="tab3" title="Tab1">Tab3 Content</k-tab>
</k-tabs>

所以我们需要一个activeTab变量,为一个字符串,它决定了标签显示的内容是什么。

对于tabs组件,我们需要实现组件的双向绑定,有一个modelValue的属性,还有事件updatae:modelValue,还有插槽。

import {defineComponent} from 'vue';

export default defineComponent ({
    name: 'KTabs',
    props: {
        modelValue: {
            type: String,
        }
    },
    emits: ['updatae:modelValue'],
    setup(props, {slots}) {
     const tabsData = ref([])
     provide('tabs-data', tabsData)
     // 激活id
     const activeTab = ref(props.modelValue)
     provide('active-tab', activeTab)
     const changeTab = (tabId: string) => {
         activeTab.value = tabId
     }
     return () => 
     <div class={'k-tabs'}>
         {/* 导航页签 */}
         <ul className='k-tabs__nav>
             {tabsData.value.map(tab => <li onClick={() => changeTab(tab.id)} class={tab.id === activeTab.value ? 'active' : ''}>{tab.title}</li>)}
         </ul>
         {/* 内容区 */}
         {slots.default?.()}
     </div>
    }
})

对于tab组件,有两个属性和一个默认插槽,tab组件需要控制内容的显示与隐藏,只需要上层提供激活状态IdactiveTab就可以进行比较控制。

import {defineComponent} from 'vue';

export default defineComponent ({
    name: 'KTab',
    props: {
        id: {
            type: String,
            required: true
        },
        title: {
            type: String,
            required: true
        }
    },
    setup(props, {slots}) {
    // 获取当前激活项
    const activeTab = inject('active-tab') as Ref<string>
    // 获取tabsData,并将自身数据加入其中
    inject('tabs-data') as Ref<
        Array<{id: string; title: string }>
    >
    tabsData.value.push({
        id: props.id,
        title: props.title
    })
     return () => (
         <>
             {props.id === activeTab.value && (
             <div class={'k-tab'}>{slots.default?.()}</div>)}
         </>
     )
    }
})
.k-tabs{
    display: flex;
    flex-direction: column;
    
    ul.k-tabs__nav {
        list-style: none;
        display: inline-flex;
        margin: 0;
        padding: 0;
        border-bottom: 2px solid #e4e7ed;
        cursor: pointer;
        
        li {
            margin-left: 32pxp;
            margin-bottom: -2px;
            color: #303133;
            padding-bottom: 4px;
        }
        
        &:first-child {
            margin-left: 0;
        }
        
        &.active {
            font-weight: 700;
            border-bottom: 2px solid #409eff;
            color: #409eff;
            
            svg {
                fill: #409eff;
            }
        }
        
        &:hover: {
            color: #409eff;
            
            svg {
                fill: #409eff;
            }
        }
    }
}