(tab): 添加滚动

This commit is contained in:
sight 2022-06-03 13:45:20 +08:00
parent d553e38ab4
commit 833a40058e
3 changed files with 248 additions and 35 deletions

View File

@ -3,7 +3,7 @@
text-align: left !important; text-align: left !important;
} }
.layui-tab[overflow] > .layui-tab-title { .layui-tab[overflow] > .layui-tab-head > .layui-tab-title {
overflow: hidden; overflow: hidden;
} }
@ -20,6 +20,7 @@
.layui-tab-head { .layui-tab-head {
display: inline-block; display: inline-block;
overflow: hidden;
} }
.layui-tab-card .layui-tab-head { .layui-tab-card .layui-tab-head {
@ -52,6 +53,7 @@
padding: 0 15px; padding: 0 15px;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
user-select: none;
} }
.layui-tab-title li a { .layui-tab-title li a {
@ -65,6 +67,7 @@
.layui-tab-title.is-top, .layui-tab-title.is-top,
.layui-tab-title.is-bottom { .layui-tab-title.is-bottom {
width: 100%; width: 100%;
position: relative;
} }
.layui-tab-title.is-right, .layui-tab-title.is-right,
@ -84,6 +87,13 @@
margin-left: -1px; margin-left: -1px;
} }
.layui-tab-title.is-top li,
.layui-tab-title.is-bottom li {
border-bottom: 1px solid #eeeeee;
}
.layui-tab-title.is-right { .layui-tab-title.is-right {
border-left: 1px solid var(--global-neutral-color-3); border-left: 1px solid var(--global-neutral-color-3);
} }
@ -94,7 +104,7 @@
.layui-tab-title .layui-this { .layui-tab-title .layui-this {
color: #000; color: #000;
background-color: #fff background-color: #fff;
} }
.layui-tab-title .layui-this:after { .layui-tab-title .layui-this:after {
@ -167,6 +177,7 @@
.layui-tab-card>.layui-tab-head>.layui-tab-title.is-top { .layui-tab-card>.layui-tab-head>.layui-tab-title.is-top {
margin-top: -1px; margin-top: -1px;
margin-left: -1px;
} }
.layui-tab-card>.layui-tab-head>.layui-tab-title.is-right, .layui-tab-card>.layui-tab-head>.layui-tab-title.is-right,
@ -182,9 +193,11 @@
.layui-tab-card>.layui-tab-head>.layui-tab-title .layui-this:after{ .layui-tab-card>.layui-tab-head>.layui-tab-title .layui-this:after{
border-radius: 0; border-radius: 0;
} }
.layui-tab-card>.layui-tab-head>.layui-tab-title.is-bottom{ .layui-tab-card>.layui-tab-head>.layui-tab-title.is-bottom{
border-top: 1px solid var(--global-neutral-color-3); border-top: 1px solid var(--global-neutral-color-3);
margin-bottom: -2px; margin-bottom: -2px;
margin-left: -1px;
} }
.layui-tab-card>.layui-tab-head>.layui-tab-title.is-left li, .layui-tab-card>.layui-tab-head>.layui-tab-title.is-left li,
@ -213,8 +226,7 @@
border-left-color: #fff; border-left-color: #fff;
} }
.layui-tab-card>.layui-tab-head>.layui-tab-title .layui-tab-bar { .layui-tab-card>.layui-tab-head .layui-tab-bar {
height: 40px;
line-height: 40px; line-height: 40px;
border-radius: 0; border-radius: 0;
border-top: none; border-top: none;
@ -246,6 +258,13 @@
cursor: pointer; cursor: pointer;
} }
.layui-tab-bar.prev{
left: 0;
right:auto;
border-right: 1px solid var(--global-neutral-color-3) !important;
border-left: none !important;
}
.layui-tab-bar .layui-icon { .layui-tab-bar .layui-icon {
position: relative; position: relative;
display: inline-block; display: inline-block;
@ -319,9 +338,8 @@
bottom: 0px; bottom: 0px;
left: 0; left: 0;
height: 1.5px; height: 1.5px;
background-color: #409eff; background-color: var(--global-checked-color);
z-index: 2; z-index: 2;
transition: transform .3s;
list-style: none; list-style: none;
box-sizing: border-box; box-sizing: border-box;
pointer-events: none; pointer-events: none;

View File

@ -6,6 +6,7 @@ export default {
<script setup lang="ts"> <script setup lang="ts">
import "./index.less"; import "./index.less";
import { LayIcon } from "@layui/icons-vue";
import tabItem from "../tabItem/index.vue"; import tabItem from "../tabItem/index.vue";
import { import {
Component, Component,
@ -16,12 +17,12 @@ import {
Ref, Ref,
ref, ref,
watch, watch,
shallowRef, shallowRef,
onMounted, onMounted,
nextTick, nextTick,
CSSProperties, CSSProperties,
} from "vue"; } from "vue";
import { useResizeObserver } from '@vueuse/core' import { useResizeObserver } from "@vueuse/core";
export type tabPositionType = "top" | "bottom" | "left" | "right"; export type tabPositionType = "top" | "bottom" | "left" | "right";
@ -86,34 +87,153 @@ const close = function (index: number, id: any) {
emit("close", id); emit("close", id);
}; };
const activeBarRef = shallowRef<HTMLElement>(); const activeBarRef = shallowRef<HTMLElement | undefined>(undefined);
const activeEl = shallowRef<HTMLElement | undefined>(); const activeEl = shallowRef<HTMLElement | undefined>(undefined);
const tabBarStyle = ref<CSSProperties>() const tabBarStyle = ref<CSSProperties>();
const getBarStyle = () => { const getBarStyle = () => {
let offset = 0; let offset = 0;
let tabSize = 0; let tabSize = 0;
const sizeName = props.tabPosition === "top" || props.tabPosition === "bottom" ? "width" : "height"; const sizeName =
props.tabPosition === "top" || props.tabPosition === "bottom"
? "width"
: "height";
const axis = sizeName === "width" ? "X" : "Y"; const axis = sizeName === "width" ? "X" : "Y";
const position = axis === 'X' ? 'left' : 'top' const position = axis === "X" ? "left" : "top";
const el = activeEl.value; const el = activeEl.value;
if(!el || !el.parentElement) return; const activeElParentElement = navRef.value;
const rect = el.getBoundingClientRect(); if (!el || !activeElParentElement) return;
const parentRect = el.parentElement?.getBoundingClientRect(); const rect = el?.getBoundingClientRect();
const parentRect = activeElParentElement?.getBoundingClientRect();
offset = rect[position] - parentRect[position]; offset = rect[position] - parentRect[position];
tabSize = el.getBoundingClientRect()[sizeName]; tabSize = el.getBoundingClientRect()[sizeName];
return { return {
[sizeName]: `${tabSize}px`, [sizeName]: `${tabSize}px`,
transform: `translate${axis}(${offset}px)`, transform: `translate${axis}(${offset}px)`,
} // transition: `transform .3s`, // activeBar
} };
const update = () => { };
const parentEL = activeBarRef.value?.parentNode;
activeEl.value = parentEL?.querySelector(" .layui-this") as HTMLElement;
tabBarStyle.value = getBarStyle();
}
const containerSize = ""; const navRef = shallowRef<HTMLElement | undefined>(undefined);
const navSize = ""; const scrollable = ref(false);
const navOffset = ref<number>(0);
const navStyle = computed<CSSProperties>(() => {
const axis =
props.tabPosition === "top" || props.tabPosition === "bottom" ? "X" : "Y";
const position = axis === "X" ? "left" : "top";
const scrollPrevSize = scrollPrevRef.value?.[`offset${sizeName.value}`] ?? 0;
return {
transform: `translate${axis}(-${navOffset.value}px)`,
[position]: scrollable.value ? `${scrollPrevSize}px` : 0,
};
});
const sizeName = computed(() => {
return props.tabPosition === "top" || props.tabPosition === "bottom"
? "Width"
: "Height";
});
const getNavSize = function () {
let size = 0;
const nodeList = navRef.value?.querySelectorAll("li");
nodeList?.forEach((item) => {
size += item[`offset${sizeName.value}`];
});
return size;
};
const scrollPrev = function () {
if (!navRef.value) return;
const containerSize = navRef.value[`offset${sizeName.value}`];
const currentOffset = navOffset.value;
if (!currentOffset) return;
let newOffset =
currentOffset > containerSize ? currentOffset - containerSize : 0;
navOffset.value = newOffset;
};
const scrollNextRef = shallowRef<HTMLElement | undefined>(undefined);
const scrollPrevRef = shallowRef<HTMLElement | undefined>(undefined);
const scrollNext = function () {
if (!navRef.value) return;
const navSize = getNavSize();
const containerSize = navRef.value[`offset${sizeName.value}`];
const currentOffset = navOffset.value;
const scrollNextSize = scrollNextRef.value?.[`offset${sizeName.value}`] ?? 0;
const scrollPrevSize = scrollPrevRef.value?.[`offset${sizeName.value}`] ?? 0;
if (navSize - currentOffset <= containerSize) return;
let newOffset =
navSize - currentOffset > containerSize * 2
? currentOffset + containerSize
: navSize - containerSize + scrollNextSize + scrollPrevSize;
navOffset.value = newOffset;
};
const headRef = shallowRef<HTMLDivElement | undefined>(undefined);
const scrollToActiveTab = function () {
if (!scrollable.value) return;
const activeTab = activeEl.value;
const container = headRef.value;
if (!activeTab || !container) return;
const activeTabRect = activeTab?.getBoundingClientRect();
const containerRect = container?.getBoundingClientRect();
const isHorizontal = ["top", "bottom"].includes(props.tabPosition);
const currentOffset = navOffset.value;
let newOffset = currentOffset;
const navSize = getNavSize();
const scrollNextSize = scrollNextRef.value?.[`offset${sizeName.value}`] ?? 0;
const scrollPrevSize = scrollPrevRef.value?.[`offset${sizeName.value}`] ?? 0;
const maxOffset = isHorizontal
? navSize - containerRect.width + scrollNextSize + scrollPrevSize
: navSize - containerRect.height + scrollNextSize + scrollPrevSize;
if (isHorizontal) {
if (activeTabRect.left < containerRect.left) {
newOffset = currentOffset - (containerRect.left - activeTabRect.left);
newOffset -= scrollPrevSize;
}
if (activeTabRect.right > containerRect.right) {
newOffset = currentOffset + activeTabRect.right - containerRect.right;
newOffset += scrollNextSize;
}
} else {
if (activeTabRect.top < containerRect.top) {
newOffset = currentOffset - (containerRect.top - activeTabRect.top);
}
if (activeTabRect.bottom > containerRect.bottom) {
newOffset = currentOffset + (activeTabRect.bottom - containerRect.bottom);
}
}
newOffset = Math.max(newOffset, 0);
navOffset.value = Math.min(newOffset, maxOffset);
};
const update = () => {
if (!navRef.value) return;
activeEl.value = navRef.value?.querySelector(".layui-this") as HTMLElement;
tabBarStyle.value = getBarStyle();
if (props.tabPosition !== "top" && props.tabPosition !== "bottom") return; //
const navSize = getNavSize();
const containerSize = navRef.value[`offset${sizeName.value}`];
const currentOffset = navOffset.value;
const scrollNextSize = scrollNextRef.value?.[`offset${sizeName.value}`] ?? 0;
const scrollPrevSize = scrollPrevRef.value?.[`offset${sizeName.value}`] ?? 0;
if (containerSize < navSize) {
const currentOffset = navOffset.value;
scrollable.value = true;
if (navSize - currentOffset < containerSize) {
navOffset.value =
navSize - containerSize + scrollNextSize + scrollPrevSize;
}
scrollToActiveTab();
} else {
scrollable.value = false;
if (currentOffset > 0) {
navOffset.value = 0;
}
}
};
useResizeObserver(navRef, update);
watch( watch(
slotsChange, slotsChange,
@ -125,16 +245,22 @@ watch(
); );
watch( watch(
() => [props.modelValue, props.tabPosition, props.type], () => [
props.modelValue,
props.tabPosition,
props.type,
childrens.value.length,
],
async () => { async () => {
await nextTick(); await nextTick();
update(); update();
} }
) );
onMounted(() => { onMounted(() => {
update(); update();
}) scrollToActiveTab();
});
provide("active", active); provide("active", active);
provide("slotsChange", slotsChange); provide("slotsChange", slotsChange);
@ -149,21 +275,28 @@ provide("slotsChange", slotsChange);
]" ]"
> >
<div <div
ref="headRef"
:class="['layui-tab-head', props.tabPosition ? `is-${tabPosition}` : '']" :class="['layui-tab-head', props.tabPosition ? `is-${tabPosition}` : '']"
> >
<ul <ul
ref="navRef"
:class="[ :class="[
'layui-tab-title', 'layui-tab-title',
props.tabPosition ? `is-${tabPosition}` : '', props.tabPosition ? `is-${tabPosition}` : '',
]" ]"
:style="navStyle"
> >
<div ref="activeBarRef" v-if="type === 'brief'" class="layui-tab-active-bar" :style="tabBarStyle"></div> <div
ref="activeBarRef"
v-if="type === 'brief'"
class="layui-tab-active-bar"
:style="tabBarStyle"
></div>
<li <li
v-for="(children, index) in childrens" v-for="(children, index) in childrens"
:key="children.props?.id" :key="children.props?.id"
:class="[children.props?.id === active ? 'layui-this' : '']" :class="[children.props?.id === active ? 'layui-this' : '']"
@click.stop="change(children.props?.id)" @click.stop="change(children.props?.id)"
> >
{{ children.props?.title }} {{ children.props?.title }}
<i <i
@ -173,7 +306,22 @@ provide("slotsChange", slotsChange);
></i> ></i>
</li> </li>
</ul> </ul>
<span
ref="scrollPrevRef"
v-if="scrollable"
class="layui-unselect layui-tab-bar prev"
@click="scrollPrev"
>
<LayIcon type="layui-icon-left"></LayIcon>
</span>
<span
ref="scrollNextRef"
v-if="scrollable"
class="layui-unselect layui-tab-bar"
@click="scrollNext"
>
<LayIcon type="layui-icon-right"></LayIcon>
</span>
</div> </div>
<div class="layui-tab-content"> <div class="layui-tab-content">
<slot></slot> <slot></slot>

View File

@ -224,6 +224,53 @@ export default {
::: :::
::: title 动态添加
:::
::: demo
<template>
<lay-button @click="addTab">添加</lay-button>
<lay-tab type="card" allow-close v-model="current8">
<lay-tab-item v-for="a in arr2" :key="a" :title="a.title" :id="a.id" :closable="a.closable">
内容{{a.id}}
</lay-tab-item>
</lay-tab>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
let index = 4;
const current8 = ref('1')
const arr2 = ref([
{id:'1', title:'选项一', closable: false},
{id:'2', title:'选项二'},
{id:'3', title:'选项三'}
])
const addTab = function(){
index++;
arr2.value.push({
id: String(index),
title:'新选项卡' + index
})
current8.value = String(index);
}
return {
arr2,
addTab,
current8
}
}
}
</script>
:::
::: title 位置设置 ::: title 位置设置
::: :::