10 - Vue3 UI Framework - Tabs 组件

vue

标签页是非常常用的组件,接下来我们来制作一个简单的 Tabs 组件

返回阅读列表点击 这里

需求分析

我们先做一个简单的需求分析

  1. 可以选择标签页排列的方向
  2. 选中的标签页应当有下划线高亮显示
  3. 切换选中时,下划线应当有动画效果
  4. 应当允许更换颜色

那么可以整理出以下参数表格

参数含义类型可选值默认值
direction方向stringrow / columnrow
selected默认选中string子项的 name必填
color颜色string任意合法颜色值#d3c8f5

通过为子项设置 name 属性,来指定默认值

骨架

本体

通过需求分析我们可以得到如下骨架:

<template>

<div

class="jeremy-tabs"

:style="{ '--color': color }"

ref="container"

:direction="direction"

>

<div class="jeremy-tabs-titles">

<button

v-for="(title, index) in titles"

:key="index"

class="jeremy-tabs-title"

:class="{ selected: names[index] === selected }"

@click="select(index)"

:ref="

(el) => {

if (names[index] === selected) {

selectedItem = el;

}

}

"

>

{{ title }}

</button>

<div class="jeremy-tabs-indicator" ref="indicator"></div>

</div>

<div class="jeremy-tabs-divider"></div>

<div class="jeremy-tabs-content">

<component :is="content" :key="selected" />

</div>

</div>

</template>

注意

这里我们用一个 div 来充当下划线,再使用一个新的 component 来显示用户输入的内容

我们还需要为标签页创建子组件,即 Tab 组件

子组件

通过之前的分析,可以得出子组件 Tab 的骨架如下:

<template>

<div>

<slot></slot>

</div>

</template>

另外,我们还需要定义一个参数,也就是标签的标题,所以还应该有如下声明与导出:

declare const props: {

title: string;

};

export default {

install: function (Vue) {

Vue.component(this.name, this);

},

name: "JeremyTab",

props: {

title: {

type: String,

default: "标签页",

},

},

};

功能

首先,我们先在 TypeScript 中声明:

declare const props: {

direction?: "row" | "column";

selected: String;

color: String;

};

declare const context: SetupContext;

其次,再在 export default 中,写入我们的参数:

export default {

name: "JeremyTabs",

props: {

direction: {

type: String,

default: "row",

},

selected: {

type: String,

required: true,

},

color: {

type: String,

default: "#8c6fef",

},

},

};

再次,再补全 setup 方法:

  setup(props, context) {

if (!["row", "column"].includes(props.direction)) {

throw new Error("错误的方向");

}

const container = ref<HTMLDivElement>(null);

const selectedItem = ref<HTMLButtonElement>(null);

const indicator = ref<HTMLDivElement>(null);

const slots = context.slots.default();

slots.forEach((slot) => {

if (slot.type !== JeremyTab) {

throw new Error("一级子标签必须是 JeremyTab");

}

if (!slot.props) {

throw new Error("存在 JeremyTab 属性列为空");

}

if (!("title" in slot.props)) {

throw new Error("JeremyTab 缺少属性 title");

}

if (!("name" in slot.props)) {

throw new Error("JeremyTab 缺少属性 name");

}

});

const titles = slots.map((slot) => slot.props.title);

const names = slots.map((slot) => slot.props.name);

if (!names.includes(props.selected)) {

throw new Error("指定了不存在的 selected 值");

}

const content = computed(() =>

slots.find((slot) => slot.props.name === props.selected)

);

onMounted(() => {

watchEffect(

() => {

if (props.direction === "row") {

const { height } = selectedItem.value.getBoundingClientRect();

indicator.value.style.top = height + "px";

const { width } = selectedItem.value.getBoundingClientRect();

indicator.value.style.width = width + "px";

const left1 = container.value.getBoundingClientRect().left;

const left2 = selectedItem.value.getBoundingClientRect().left;

const left = left2 - left1;

indicator.value.style.left = left + "px";

} else {

const { height } = selectedItem.value.getBoundingClientRect();

indicator.value.style.height = height + "px";

const { width } = selectedItem.value.getBoundingClientRect();

indicator.value.style.left = width + "px";

const top1 = container.value.getBoundingClientRect().top;

const top2 = selectedItem.value.getBoundingClientRect().top;

const top = top2 - top1;

indicator.value.style.top = top + "px";

}

},

{ flush: "post" }

);

});

const select = (index) => {

context.emit("update:selected", names[index]);

};

return {

container,

selectedItem,

indicator,

slots,

titles,

names,

content,

select,

};

},

样式表

最后,再补全样式表

$theme-color: var(--color);

.jeremy-tabs {

display: flex;

flex-direction: column;

position: relative;

&-titles {

display: flex;

}

&-title {

padding: 4px 6px;

border: none;

cursor: pointer;

outline: none;

background: white;

&:focus {

outline: none;

}

&:hover {

color: $theme-color;

}

&.selected {

color: $theme-color;

}

}

&-indicator {

position: absolute;

transition: all 250ms;

border: 1px solid $theme-color;

}

&-divider {

border: 1px solid rgb(184, 184, 184);

}

&-content {

padding: 8px 4px;

}

}

.jeremy-tabs[direction="column"] {

flex-direction: row;

> .jeremy-tabs-titles {

flex-direction: column;

}

> .jeremy-tabs-content {

padding: 2px 10px;

}

}

测试

JeremyTabs 组件引入到测试文档,查看一下运行效果

项目地址

以上是 10 - Vue3 UI Framework - Tabs 组件 的全部内容, 来源链接: utcz.com/z/374785.html

回到顶部